From 33e6b59654dbbe313c6153138c0a703083077155 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 27 Aug 2026 16:48:21 -0400 Subject: [PATCH 1/4] chore: enable ruff pydocstyle (D) rules for docstring lint + formatting Enable Ruff's pydocstyle `D` rules with the google convention and `docstring-code-format`, and fix every resulting violation on docstrings that already exist across slack_bolt/ and examples/. - pyproject.toml: select "D" (google convention); ignore only the missing-docstring rules D100-D107 so no docstrings are invented where none exist; enable docstring-code-format. - Fix all D2xx/D4xx violations on existing docstrings (D205 blank line after summary, D415 terminal punctuation, D417 undocumented params, and safe structural fixes). No docstrings added. - scripts/format.sh: format examples/ too. - scripts/lint.sh: add `ruff format --check` gate so local matches CI. Sync/async docstring pairs updated symmetrically. Co-Authored-By: Claude --- examples/django/manage.py | 1 + examples/django/myslackapp/asgi.py | 3 +- examples/django/myslackapp/settings.py | 4 +- examples/django/myslackapp/urls.py | 4 +- examples/django/myslackapp/wsgi.py | 3 +- .../oauth_app/migrations/0001_initial.py | 1 - .../migrations/0002_token_rotation.py | 1 - examples/google_cloud_functions/oauth_main.py | 1 + .../google_cloud_functions/simple_main.py | 1 + examples/message_events.py | 3 +- pyproject.toml | 26 ++++- scripts/format.sh | 2 +- scripts/lint.sh | 1 + slack_bolt/__init__.py | 3 +- slack_bolt/adapter/asgi/aiohttp/__init__.py | 1 + slack_bolt/adapter/asgi/base_handler.py | 6 +- slack_bolt/adapter/asgi/builtin/__init__.py | 1 + slack_bolt/adapter/django/handler.py | 2 + slack_bolt/adapter/falcon/async_resource.py | 3 +- slack_bolt/adapter/falcon/resource.py | 6 +- .../adapter/socket_mode/aiohttp/__init__.py | 4 +- .../adapter/socket_mode/async_base_handler.py | 11 +- .../adapter/socket_mode/async_internals.py | 2 +- .../adapter/socket_mode/base_handler.py | 10 +- .../adapter/socket_mode/builtin/__init__.py | 4 +- slack_bolt/adapter/socket_mode/internals.py | 2 +- .../socket_mode/websocket_client/__init__.py | 4 +- .../socket_mode/websockets/__init__.py | 2 +- slack_bolt/adapter/wsgi/handler.py | 1 + slack_bolt/adapter/wsgi/http_request.py | 3 +- slack_bolt/adapter/wsgi/http_response.py | 3 +- slack_bolt/app/app.py | 53 ++++++--- slack_bolt/app/async_app.py | 49 +++++++-- slack_bolt/app/async_server.py | 1 + slack_bolt/async_app.py | 2 +- slack_bolt/authorization/__init__.py | 3 +- slack_bolt/authorization/async_authorize.py | 8 +- slack_bolt/authorization/authorize.py | 8 +- slack_bolt/authorization/authorize_result.py | 7 +- slack_bolt/context/__init__.py | 1 + slack_bolt/context/assistant/internals.py | 1 + slack_bolt/context/async_context.py | 8 +- slack_bolt/context/base_context.py | 8 +- slack_bolt/context/context.py | 8 +- slack_bolt/error/__init__.py | 2 +- slack_bolt/kwargs_injection/args.py | 1 + slack_bolt/kwargs_injection/async_args.py | 1 + slack_bolt/listener/__init__.py | 7 +- slack_bolt/listener/async_builtins.py | 2 +- .../async_listener_completion_handler.py | 2 +- .../listener/async_listener_start_handler.py | 2 +- slack_bolt/listener/builtins.py | 2 +- .../listener/listener_completion_handler.py | 2 +- slack_bolt/listener_matcher/__init__.py | 1 + slack_bolt/middleware/__init__.py | 5 +- slack_bolt/middleware/async_middleware.py | 3 +- .../single_team_authorization.py | 1 + slack_bolt/middleware/middleware.py | 3 +- .../async_request_verification.py | 5 +- .../request_verification.py | 5 +- slack_bolt/middleware/ssl_check/ssl_check.py | 1 + slack_bolt/request/async_request.py | 1 - slack_bolt/version.py | 2 +- slack_bolt/workflows/step/async_step.py | 52 ++++----- .../workflows/step/async_step_middleware.py | 2 +- slack_bolt/workflows/step/step.py | 53 ++++----- slack_bolt/workflows/step/step_middleware.py | 2 +- .../logger/test_unmatched_suggestions.py | 102 ++++++++++++------ 68 files changed, 336 insertions(+), 199 deletions(-) diff --git a/examples/django/manage.py b/examples/django/manage.py index cec2fe4e4..6e66426e5 100755 --- a/examples/django/manage.py +++ b/examples/django/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/examples/django/myslackapp/asgi.py b/examples/django/myslackapp/asgi.py index 814c38df4..39a2a58ee 100644 --- a/examples/django/myslackapp/asgi.py +++ b/examples/django/myslackapp/asgi.py @@ -1,5 +1,4 @@ -""" -ASGI config for myslackapp project. +"""ASGI config for myslackapp project. It exposes the ASGI callable as a module-level variable named ``application``. diff --git a/examples/django/myslackapp/settings.py b/examples/django/myslackapp/settings.py index a99c91188..4cde94ac9 100644 --- a/examples/django/myslackapp/settings.py +++ b/examples/django/myslackapp/settings.py @@ -1,5 +1,4 @@ -""" -Django settings for myslackapp project. +"""Django settings for myslackapp project. Generated by 'django-admin startproject' using Django 3.2.3. @@ -9,6 +8,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.2/ref/settings/ """ + import os from pathlib import Path diff --git a/examples/django/myslackapp/urls.py b/examples/django/myslackapp/urls.py index f3d5c7268..6e5db7b8f 100644 --- a/examples/django/myslackapp/urls.py +++ b/examples/django/myslackapp/urls.py @@ -1,7 +1,8 @@ -"""myslackapp URL Configuration +"""myslackapp URL Configuration. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.2/topics/http/urls/ + Examples: Function views 1. Add an import: from my_app import views @@ -13,6 +14,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin # noqa: F401 from django.urls import path diff --git a/examples/django/myslackapp/wsgi.py b/examples/django/myslackapp/wsgi.py index 4443e81cb..717fcb796 100644 --- a/examples/django/myslackapp/wsgi.py +++ b/examples/django/myslackapp/wsgi.py @@ -1,5 +1,4 @@ -""" -WSGI config for myslackapp project. +"""WSGI config for myslackapp project. It exposes the WSGI callable as a module-level variable named ``application``. diff --git a/examples/django/oauth_app/migrations/0001_initial.py b/examples/django/oauth_app/migrations/0001_initial.py index d5e3113c6..2d7b9cfed 100644 --- a/examples/django/oauth_app/migrations/0001_initial.py +++ b/examples/django/oauth_app/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] diff --git a/examples/django/oauth_app/migrations/0002_token_rotation.py b/examples/django/oauth_app/migrations/0002_token_rotation.py index 65648d6b2..27a67c72d 100644 --- a/examples/django/oauth_app/migrations/0002_token_rotation.py +++ b/examples/django/oauth_app/migrations/0002_token_rotation.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("oauth_app", "0001_initial"), ] diff --git a/examples/google_cloud_functions/oauth_main.py b/examples/google_cloud_functions/oauth_main.py index 773eafcf3..190f9d000 100644 --- a/examples/google_cloud_functions/oauth_main.py +++ b/examples/google_cloud_functions/oauth_main.py @@ -52,6 +52,7 @@ def event_test(body, say, logger): # Cloud Function def hello_bolt_app(req: Request): """HTTP Cloud Function. + Args: req (flask.Request): The request object. diff --git a/examples/google_cloud_functions/simple_main.py b/examples/google_cloud_functions/simple_main.py index 9ff839aca..bab5e85f1 100644 --- a/examples/google_cloud_functions/simple_main.py +++ b/examples/google_cloud_functions/simple_main.py @@ -32,6 +32,7 @@ def event_test(body, say, logger): # Cloud Function def hello_bolt_app(req: Request): """HTTP Cloud Function. + Args: req (flask.Request): The request object. diff --git a/examples/message_events.py b/examples/message_events.py index 3fd424060..71ef91a4e 100644 --- a/examples/message_events.py +++ b/examples/message_events.py @@ -46,8 +46,7 @@ def reply_in_thread(body: dict, say: Say): event={"type": "message", "subtype": "message_deleted"}, matchers=[ # Skip the deletion of messages by this listener - lambda body: "You've deleted a message: " - not in body["event"]["previous_message"]["text"] + lambda body: "You've deleted a message: " not in body["event"]["previous_message"]["text"] ], ) def detect_deletion(say: Say, body: dict): diff --git a/pyproject.toml b/pyproject.toml index c7094dd4b..7110dff4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,30 @@ universal = true line-length = 125 [tool.ruff.lint] -select = ["E", "W", "F"] -ignore = ["F841", "F821", "E402"] +select = ["E", "W", "F", "D"] +ignore = [ + "F841", + "F821", + "E402", + # missing-docstring: do not add docstrings where none exist (the only D rules ignored) + "D100", + "D101", + "D102", + "D103", + "D104", + "D105", + "D106", + "D107", +] + +[tool.ruff.lint.pydocstyle] +# google convention resolves D203/D211 and D212/D213 conflicts and implicitly ignores +# D400/D401/D404/D413. Keep `select = ["D"]` a category select -- an explicit rule-code +# select would override the convention and resurface those rules. +convention = "google" + +[tool.ruff.format] +docstring-code-format = true [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/scripts/format.sh b/scripts/format.sh index c25146d2f..92fe05dde 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -11,4 +11,4 @@ if [[ "$1" != "--no-install" ]]; then fi ruff check --fix slack_bolt/ examples/ -ruff format slack_bolt/ tests/ +ruff format slack_bolt/ tests/ examples/ diff --git a/scripts/lint.sh b/scripts/lint.sh index f7f2b605b..c6176d0d1 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -10,3 +10,4 @@ if [[ "$1" != "--no-install" ]]; then fi ruff check slack_bolt/ examples/ +ruff format --check slack_bolt/ tests/ examples/ diff --git a/slack_bolt/__init__.py b/slack_bolt/__init__.py index e3664814b..038fde4a8 100644 --- a/slack_bolt/__init__.py +++ b/slack_bolt/__init__.py @@ -1,5 +1,4 @@ -""" -A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/creating-an-app) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. +"""A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/creating-an-app) 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://docs.slack.dev/tools/bolt-python/ * GitHub repository: https://github.com/slackapi/bolt-python diff --git a/slack_bolt/adapter/asgi/aiohttp/__init__.py b/slack_bolt/adapter/asgi/aiohttp/__init__.py index aed8458d9..796098537 100644 --- a/slack_bolt/adapter/asgi/aiohttp/__init__.py +++ b/slack_bolt/adapter/asgi/aiohttp/__init__.py @@ -12,6 +12,7 @@ class AsyncSlackRequestHandler(SlackRequestHandler): def __init__(self, app: AsyncApp, path: str = "/slack/events"): """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. + This can be used for production deployment. With the default settings, `http://localhost:3000/slack/events` diff --git a/slack_bolt/adapter/asgi/base_handler.py b/slack_bolt/adapter/asgi/base_handler.py index 3acce78da..2926796a7 100644 --- a/slack_bolt/adapter/asgi/base_handler.py +++ b/slack_bolt/adapter/asgi/base_handler.py @@ -18,15 +18,15 @@ class BaseSlackRequestHandler: path: str async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse: - """Dispatches a request to the Bolt App""" + """Dispatches a request to the Bolt App.""" raise NotImplementedError async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse: - """Handles installation of the OAuthFlow""" + """Handles installation of the OAuthFlow.""" raise NotImplementedError async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse: - """Handles the callback of the OAuthFlow""" + """Handles the callback of the OAuthFlow.""" raise NotImplementedError async def _get_http_response(self, method: str, path: str, request: AsgiHttpRequest) -> AsgiHttpResponse: diff --git a/slack_bolt/adapter/asgi/builtin/__init__.py b/slack_bolt/adapter/asgi/builtin/__init__.py index 93f7ab845..b05451f34 100644 --- a/slack_bolt/adapter/asgi/builtin/__init__.py +++ b/slack_bolt/adapter/asgi/builtin/__init__.py @@ -11,6 +11,7 @@ class SlackRequestHandler(BaseSlackRequestHandler): def __init__(self, app: App, path: str = "/slack/events"): """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. + This can be used for production deployment. With the default settings, `http://localhost:3000/slack/events` diff --git a/slack_bolt/adapter/django/handler.py b/slack_bolt/adapter/django/handler.py index 8f5b0d372..b01323a10 100644 --- a/slack_bolt/adapter/django/handler.py +++ b/slack_bolt/adapter/django/handler.py @@ -72,6 +72,7 @@ def release_thread_local_connections(logger: Logger, execution_timing: str): class DjangoListenerStartHandler(ListenerStartHandler): """Django sets DB connections as a thread-local variable per thread. + If the thread is not managed on the Django app side, the connections won't be released by Django. This handler releases the connections every time a ThreadListenerRunner execution completes. """ @@ -82,6 +83,7 @@ def handle(self, request: BoltRequest, response: Optional[BoltResponse]) -> None class DjangoListenerCompletionHandler(ListenerCompletionHandler): """Django sets DB connections as a thread-local variable per thread. + If the thread is not managed on the Django app side, the connections won't be released by Django. This handler releases the connections every time a ThreadListenerRunner execution completes. """ diff --git a/slack_bolt/adapter/falcon/async_resource.py b/slack_bolt/adapter/falcon/async_resource.py index fdb2d975f..1c22c83a0 100644 --- a/slack_bolt/adapter/falcon/async_resource.py +++ b/slack_bolt/adapter/falcon/async_resource.py @@ -12,8 +12,7 @@ class AsyncSlackAppResource: - """ - For use with ASGI Falcon Apps. + """For use with ASGI Falcon Apps. from slack_bolt.async_app import AsyncApp app = AsyncApp() diff --git a/slack_bolt/adapter/falcon/resource.py b/slack_bolt/adapter/falcon/resource.py index 5d162ad23..66e75d818 100644 --- a/slack_bolt/adapter/falcon/resource.py +++ b/slack_bolt/adapter/falcon/resource.py @@ -11,9 +11,9 @@ class SlackAppResource: - """ - from slack_bolt import App - app = App() + """from slack_bolt import App. + + app = App(). import falcon api = application = falcon.API() diff --git a/slack_bolt/adapter/socket_mode/aiohttp/__init__.py b/slack_bolt/adapter/socket_mode/aiohttp/__init__.py index 124daaa4a..b117df790 100644 --- a/slack_bolt/adapter/socket_mode/aiohttp/__init__.py +++ b/slack_bolt/adapter/socket_mode/aiohttp/__init__.py @@ -1,4 +1,4 @@ -"""[`aiohttp`](https://pypi.org/project/aiohttp/) based implementation / asyncio compatible""" +"""[`aiohttp`](https://pypi.org/project/aiohttp/) based implementation / asyncio compatible.""" import os from logging import Logger @@ -35,7 +35,7 @@ def __init__( proxy: Optional[str] = None, ping_interval: float = 10, ): - """Socket Mode adapter for Bolt apps + """Socket Mode adapter for Bolt apps. Args: app: The Bolt app diff --git a/slack_bolt/adapter/socket_mode/async_base_handler.py b/slack_bolt/adapter/socket_mode/async_base_handler.py index 32ddaff14..aeb41d5a2 100644 --- a/slack_bolt/adapter/socket_mode/async_base_handler.py +++ b/slack_bolt/adapter/socket_mode/async_base_handler.py @@ -1,4 +1,4 @@ -"""The base class of asyncio-based Socket Mode client implementation""" +"""The base class of asyncio-based Socket Mode client implementation.""" import asyncio import logging @@ -26,19 +26,20 @@ async def handle(self, client: AsyncBaseSocketModeClient, req: SocketModeRequest raise NotImplementedError() async def connect_async(self): - """Establishes a new connection with the Socket Mode server""" + """Establishes a new connection with the Socket Mode server.""" await self.client.connect() async def disconnect_async(self): - """Disconnects the current WebSocket connection with the Socket Mode server""" + """Disconnects the current WebSocket connection with the Socket Mode server.""" await self.client.disconnect() async def close_async(self): - """Disconnects from the Socket Mode server and cleans the resources this instance holds up""" + """Disconnects from the Socket Mode server and cleans the resources this instance holds up.""" await self.client.close() async def start_async(self): - """Establishes a new connection and then starts infinite sleep + """Establishes a new connection and then starts infinite sleep. + to prevent the termination of this process. If you don't want to have the sleep, use `#connect()` method instead. """ diff --git a/slack_bolt/adapter/socket_mode/async_internals.py b/slack_bolt/adapter/socket_mode/async_internals.py index 428ab437c..5fefd7400 100644 --- a/slack_bolt/adapter/socket_mode/async_internals.py +++ b/slack_bolt/adapter/socket_mode/async_internals.py @@ -1,4 +1,4 @@ -"""Internal functions""" +"""Internal functions.""" import json import logging diff --git a/slack_bolt/adapter/socket_mode/base_handler.py b/slack_bolt/adapter/socket_mode/base_handler.py index 432a327b9..6b36ce014 100644 --- a/slack_bolt/adapter/socket_mode/base_handler.py +++ b/slack_bolt/adapter/socket_mode/base_handler.py @@ -1,4 +1,5 @@ """The base class of Socket Mode client implementation. + If you want to build asyncio-based ones, use `AsyncBaseSocketModeHandler` instead. """ @@ -28,19 +29,20 @@ def handle(self, client: BaseSocketModeClient, req: SocketModeRequest) -> None: raise NotImplementedError() def connect(self): - """Establishes a new connection with the Socket Mode server""" + """Establishes a new connection with the Socket Mode server.""" self.client.connect() def disconnect(self): - """Disconnects the current WebSocket connection with the Socket Mode server""" + """Disconnects the current WebSocket connection with the Socket Mode server.""" self.client.disconnect() def close(self): - """Disconnects from the Socket Mode server and cleans the resources this instance holds up""" + """Disconnects from the Socket Mode server and cleans the resources this instance holds up.""" self.client.close() def start(self): - """Establishes a new connection and then blocks the current thread + """Establishes a new connection and then blocks the current thread. + to prevent the termination of this process. If you don't want to block the current thread, use `#connect()` method instead. """ diff --git a/slack_bolt/adapter/socket_mode/builtin/__init__.py b/slack_bolt/adapter/socket_mode/builtin/__init__.py index 6dbc9562d..397724039 100644 --- a/slack_bolt/adapter/socket_mode/builtin/__init__.py +++ b/slack_bolt/adapter/socket_mode/builtin/__init__.py @@ -1,4 +1,4 @@ -"""The built-in implementation, which does not have any external dependencies""" +"""The built-in implementation, which does not have any external dependencies.""" import os from logging import Logger @@ -36,7 +36,7 @@ def __init__( receive_buffer_size: int = 1024, concurrency: int = 10, ): - """Socket Mode adapter for Bolt apps + """Socket Mode adapter for Bolt apps. Args: app: The Bolt app diff --git a/slack_bolt/adapter/socket_mode/internals.py b/slack_bolt/adapter/socket_mode/internals.py index 6289f28f5..b57a1c3d7 100644 --- a/slack_bolt/adapter/socket_mode/internals.py +++ b/slack_bolt/adapter/socket_mode/internals.py @@ -1,4 +1,4 @@ -"""Internal functions""" +"""Internal functions.""" import json import logging diff --git a/slack_bolt/adapter/socket_mode/websocket_client/__init__.py b/slack_bolt/adapter/socket_mode/websocket_client/__init__.py index aae549ad6..b6acac619 100644 --- a/slack_bolt/adapter/socket_mode/websocket_client/__init__.py +++ b/slack_bolt/adapter/socket_mode/websocket_client/__init__.py @@ -1,4 +1,4 @@ -"""[`websocket-client`](https://pypi.org/project/websocket-client/) based implementation""" +"""[`websocket-client`](https://pypi.org/project/websocket-client/) based implementation.""" import os from logging import Logger @@ -34,7 +34,7 @@ def __init__( proxy_type: Optional[str] = None, trace_enabled: bool = False, ): - """Socket Mode adapter for Bolt apps + """Socket Mode adapter for Bolt apps. Args: app: The Bolt app diff --git a/slack_bolt/adapter/socket_mode/websockets/__init__.py b/slack_bolt/adapter/socket_mode/websockets/__init__.py index 049a20570..ce1665c5d 100644 --- a/slack_bolt/adapter/socket_mode/websockets/__init__.py +++ b/slack_bolt/adapter/socket_mode/websockets/__init__.py @@ -1,4 +1,4 @@ -"""[`websockets`](https://pypi.org/project/websockets/) based implementation / asyncio compatible""" +"""[`websockets`](https://pypi.org/project/websockets/) based implementation / asyncio compatible.""" import os from logging import Logger diff --git a/slack_bolt/adapter/wsgi/handler.py b/slack_bolt/adapter/wsgi/handler.py index fef54f73e..b13e64530 100644 --- a/slack_bolt/adapter/wsgi/handler.py +++ b/slack_bolt/adapter/wsgi/handler.py @@ -14,6 +14,7 @@ class SlackRequestHandler: def __init__(self, app: App, path: str = "/slack/events"): """Setup Bolt as a WSGI web framework, this will make your application compatible with WSGI web servers. + This can be used for production deployments. With the default settings, `http://localhost:3000/slack/events` diff --git a/slack_bolt/adapter/wsgi/http_request.py b/slack_bolt/adapter/wsgi/http_request.py index 644d0e333..ac4c434bd 100644 --- a/slack_bolt/adapter/wsgi/http_request.py +++ b/slack_bolt/adapter/wsgi/http_request.py @@ -7,8 +7,7 @@ class WsgiHttpRequest: - """This Class uses the PEP 3333 standard to extract request information - from the WSGI web server running the application + """Extracts request information from the WSGI web server using the PEP 3333 standard. PEP 3333: https://peps.python.org/pep-3333/ """ diff --git a/slack_bolt/adapter/wsgi/http_response.py b/slack_bolt/adapter/wsgi/http_response.py index 32956d276..32feff3d0 100644 --- a/slack_bolt/adapter/wsgi/http_response.py +++ b/slack_bolt/adapter/wsgi/http_response.py @@ -5,8 +5,7 @@ class WsgiHttpResponse: - """This Class uses the PEP 3333 standard to adapt bolt response information - for the WSGI web server running the application + """Adapts bolt response information for the WSGI web server using the PEP 3333 standard. PEP 3333: https://peps.python.org/pep-3333/ """ diff --git a/slack_bolt/app/app.py b/slack_bolt/app/app.py index 0fa3008bb..2b309db4b 100644 --- a/slack_bolt/app/app.py +++ b/slack_bolt/app/app.py @@ -207,6 +207,10 @@ def message_hello(message, say): be used. assistant_thread_context_store: Custom AssistantThreadContext store (Default: the built-in implementation, which uses a parent message's metadata to store the latest context) + attaching_conversation_kwargs_enabled: False if you would like to disable the built-in + middleware (Default: True). `AttachingConversationKwargs` is a built-in middleware that attaches + conversation-specific listener arguments (such as `say`, `set_status`, `say_stream`, and + `set_suggested_prompts`) for assistant thread and direct message events. """ if signing_secret is None: signing_secret = os.environ.get("SLACK_SIGNING_SECRET", "") @@ -468,7 +472,7 @@ def _init_middleware_list( @property def name(self) -> str: - """The name of this app (default: the filename)""" + """The name of this app (default: the filename).""" return self._name @property @@ -653,11 +657,13 @@ def _handle_unmatched_requests(self, req: BoltRequest, resp: BoltResponse) -> Bo 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.""" + 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 @@ -712,8 +718,8 @@ def step( save: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None, execute: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None, ): - """ - Deprecated: + """Deprecated: register a new step from app listener. + Steps from apps for legacy workflows are now deprecated. Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ @@ -857,6 +863,7 @@ def message( 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 @@ -919,6 +926,7 @@ def function( ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -943,8 +951,12 @@ def reverse_string(ack: Ack, inputs: dict, complete: Complete, fail: Fail): 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. + auto_acknowledge: Whether Bolt automatically acknowledges the function execution event on the + listener's behalf. When False, your listener must call `ack()` itself within `ack_timeout` + seconds (Default: True). + ack_timeout: The number of seconds to wait for the listener to call `ack()`. + Only takes effect when `auto_acknowledge` is False (Default: 3). """ - if auto_acknowledge is True: if ack_timeout != 3: self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) @@ -969,6 +981,7 @@ def command( 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 @@ -1010,6 +1023,7 @@ def shortcut( 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 @@ -1124,6 +1138,7 @@ def block_action( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ @@ -1141,7 +1156,9 @@ def attachment_action( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1157,7 +1174,9 @@ def dialog_submission( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1173,7 +1192,9 @@ def dialog_cancellation( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1192,6 +1213,7 @@ def view( 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 @@ -1206,7 +1228,7 @@ def handle_submission(ack, body, client, view): errors["block_c"] = "The value must be longer than 5 characters" if len(errors) > 0: ack(response_action="errors", errors=errors) - return + return # Return early to display the validation errors to the user # 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 @@ -1240,6 +1262,7 @@ def view_submission( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details. """ @@ -1258,7 +1281,9 @@ def view_closed( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" + + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1277,6 +1302,7 @@ def options( 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 @@ -1305,6 +1331,7 @@ def show_menu_options(ack): 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. @@ -1340,7 +1367,9 @@ def dialog_suggestion( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1469,7 +1498,7 @@ def __init__( oauth_flow: Optional[OAuthFlow] = None, http_server_logger_enabled: bool = True, ): - """Slack App Development Server + """Slack App Development Server. This is a thin wrapper of http.server.HTTPServer and is good enough for your local development or prototyping. diff --git a/slack_bolt/app/async_app.py b/slack_bolt/app/async_app.py index 262fd9a3a..ebe7e315a 100644 --- a/slack_bolt/app/async_app.py +++ b/slack_bolt/app/async_app.py @@ -213,6 +213,10 @@ async def message_hello(message, say): # async function verification_token: Deprecated verification mechanism. This can be used only for ssl_check requests. assistant_thread_context_store: Custom AssistantThreadContext store (Default: the built-in implementation, which uses a parent message's metadata to store the latest context) + attaching_conversation_kwargs_enabled: False if you would like to disable the built-in + middleware (Default: True). `AttachingConversationKwargs` is a built-in middleware that attaches + conversation-specific listener arguments (such as `say`, `set_status`, `say_stream`, and + `set_suggested_prompts`) for assistant thread and direct message events. """ if signing_secret is None: signing_secret = os.environ.get("SLACK_SIGNING_SECRET", "") @@ -467,7 +471,7 @@ def _init_async_middleware_list( @property def name(self) -> str: - """The name of this app (default: the filename)""" + """The name of this app (default: the filename).""" return self._name @property @@ -511,6 +515,7 @@ def server( host: Optional[str] = None, ) -> AsyncSlackAppServer: """Configure a web server using AIOHTTP. + Refer to https://docs.aiohttp.org/ for more details about AIOHTTP. Args: @@ -551,6 +556,7 @@ def app_factory(): def start(self, port: int = 3000, path: str = "/slack/events", host: Optional[str] = None) -> None: """Start a web server using AIOHTTP. + Refer to https://docs.aiohttp.org/ for more details about AIOHTTP. Args: @@ -687,6 +693,7 @@ def use(self, *args) -> Optional[Callable]: 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 @@ -736,8 +743,8 @@ def step( save: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None, execute: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None, ): - """ - Deprecated: + """Deprecated: register a new step from app listener. + Steps from apps for legacy workflows are now deprecated. Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ @@ -885,6 +892,7 @@ def message( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new message event listener. This method can be used as either a decorator or a method. + Check the `App#event` method's docstring for details. # Use this method as a decorator @@ -950,6 +958,7 @@ def function( ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -974,6 +983,11 @@ async def reverse_string(ack: AsyncAck, inputs: dict, complete: AsyncComplete, f 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. + auto_acknowledge: Whether Bolt automatically acknowledges the function execution event on the + listener's behalf. When False, your listener must call `ack()` itself within `ack_timeout` + seconds (Default: True). + ack_timeout: The number of seconds to wait for the listener to call `ack()`. + Only takes effect when `auto_acknowledge` is False (Default: 3). """ if auto_acknowledge is True: if ack_timeout != 3: @@ -1001,6 +1015,7 @@ def command( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new slash command listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -1042,6 +1057,7 @@ def shortcut( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new shortcut listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -1156,6 +1172,7 @@ def block_action( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ @@ -1173,7 +1190,9 @@ def attachment_action( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1189,7 +1208,9 @@ def dialog_submission( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1205,7 +1226,9 @@ def dialog_cancellation( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1224,6 +1247,7 @@ def view( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission`/`view_closed` event listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -1238,7 +1262,7 @@ async def handle_submission(ack, body, client, view): errors["block_c"] = "The value must be longer than 5 characters" if len(errors) > 0: await ack(response_action="errors", errors=errors) - return + return # Return early to display the validation errors to the user # Acknowledge the view_submission event and close the modal await ack() # Do whatever you want with the input data - here we're saving it to a DB @@ -1272,6 +1296,7 @@ def view_submission( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details. """ @@ -1290,7 +1315,9 @@ def view_closed( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" + + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1309,6 +1336,7 @@ def options( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new options listener. + This method can be used as either a decorator or a method. # Use this method as a decorator @@ -1337,6 +1365,7 @@ async def show_menu_options(ack): To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. Args: + constraints: The conditions that match a request payload matchers: A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked. middleware: A list of lister middleware functions. @@ -1372,7 +1401,9 @@ def dialog_suggestion( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" + + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) diff --git a/slack_bolt/app/async_server.py b/slack_bolt/app/async_server.py index f21d35932..9511a5f7b 100644 --- a/slack_bolt/app/async_server.py +++ b/slack_bolt/app/async_server.py @@ -26,6 +26,7 @@ def __init__( host: Optional[str] = None, ): """Standalone AIOHTTP Web Server. + Refer to https://docs.aiohttp.org/en/stable/web.html for details of AIOHTTP. Args: diff --git a/slack_bolt/async_app.py b/slack_bolt/async_app.py index f95d952aa..f2e2fbfa8 100644 --- a/slack_bolt/async_app.py +++ b/slack_bolt/async_app.py @@ -1,4 +1,4 @@ -"""Module for creating asyncio based apps +"""Module for creating asyncio based apps. ### Creating an async app diff --git a/slack_bolt/authorization/__init__.py b/slack_bolt/authorization/__init__.py index 4b80a93bb..da6fa4b98 100644 --- a/slack_bolt/authorization/__init__.py +++ b/slack_bolt/authorization/__init__.py @@ -1,5 +1,4 @@ -"""Authorization is the process of determining which Slack credentials should be available -while processing an incoming Slack event. +"""Authorization determines which Slack credentials should be available while processing an incoming Slack event. Refer to https://docs.slack.dev/tools/bolt-python/concepts/authorization for details. """ diff --git a/slack_bolt/authorization/async_authorize.py b/slack_bolt/authorization/async_authorize.py index 7fa04a304..e1aece4fd 100644 --- a/slack_bolt/authorization/async_authorize.py +++ b/slack_bolt/authorization/async_authorize.py @@ -17,8 +17,7 @@ class AsyncAuthorize: - """This provides authorize function that returns AuthorizeResult - for an incoming request from Slack.""" + """This provides authorize function that returns AuthorizeResult for an incoming request from Slack.""" def __init__(self): pass @@ -39,9 +38,7 @@ async def __call__( class AsyncCallableAuthorize(AsyncAuthorize): - """When you pass the authorize argument in AsyncApp constructor, - This authorize implementation will be used. - """ + """When you pass the authorize argument in AsyncApp constructor, this authorize implementation will be used.""" def __init__(self, *, logger: Logger, func: Callable[..., Awaitable[AuthorizeResult]]): self.logger = logger @@ -107,6 +104,7 @@ async def __call__( class AsyncInstallationStoreAuthorize(AsyncAuthorize): """If you use the OAuth flow settings, this authorize implementation will be used. + As long as your own InstallationStore (or the built-in ones) works as you expect, you can expect that the authorize layer should work for you without any customization. """ diff --git a/slack_bolt/authorization/authorize.py b/slack_bolt/authorization/authorize.py index e0f62fed4..bcd19af72 100644 --- a/slack_bolt/authorization/authorize.py +++ b/slack_bolt/authorization/authorize.py @@ -16,8 +16,7 @@ class Authorize: - """This provides authorize function that returns AuthorizeResult - for an incoming request from Slack.""" + """This provides authorize function that returns AuthorizeResult for an incoming request from Slack.""" def __init__(self): pass @@ -38,9 +37,7 @@ def __call__( class CallableAuthorize(Authorize): - """When you pass the `authorize` argument in AsyncApp constructor, - This `authorize` implementation will be used. - """ + """When you pass the `authorize` argument in App constructor, this `authorize` implementation will be used.""" def __init__( self, @@ -111,6 +108,7 @@ def __call__( class InstallationStoreAuthorize(Authorize): """If you use the OAuth flow settings, this `authorize` implementation will be used. + As long as your own InstallationStore (or the built-in ones) works as you expect, you can expect that the `authorize` layer should work for you without any customization. """ diff --git a/slack_bolt/authorization/authorize_result.py b/slack_bolt/authorization/authorize_result.py index cbf1a4678..4a7b0e656 100644 --- a/slack_bolt/authorization/authorize_result.py +++ b/slack_bolt/authorization/authorize_result.py @@ -4,7 +4,7 @@ class AuthorizeResult(dict): - """Authorize function call result""" + """Authorize function call result.""" enterprise_id: Optional[str] team_id: Optional[str] @@ -39,7 +39,8 @@ def __init__( user_token: Optional[str] = None, user_scopes: Optional[Union[Sequence[str], str]] = None, ): - """ + """Initialize the authorize function call result. + Args: enterprise_id: Organization ID (Enterprise Grid) starting with `E` team_id: Workspace ID starting with `T` @@ -52,7 +53,7 @@ def __init__( user_id: The request user ID user: The request user's name user_token: User access token starting with `xoxp-` - user_scopes: The scopes associated wth the user token + user_scopes: The scopes associated with the user token """ self["enterprise_id"] = self.enterprise_id = enterprise_id self["team_id"] = self.team_id = team_id diff --git a/slack_bolt/context/__init__.py b/slack_bolt/context/__init__.py index 865825601..5d157e42a 100644 --- a/slack_bolt/context/__init__.py +++ b/slack_bolt/context/__init__.py @@ -1,4 +1,5 @@ """All listeners have access to a context dictionary, which can be used to enrich events with additional information. + Bolt automatically attaches information that is included in the incoming event, like `user_id`, `team_id`, `channel_id`, and `enterprise_id`. diff --git a/slack_bolt/context/assistant/internals.py b/slack_bolt/context/assistant/internals.py index ee449c31b..15fc55d80 100644 --- a/slack_bolt/context/assistant/internals.py +++ b/slack_bolt/context/assistant/internals.py @@ -1,5 +1,6 @@ def has_channel_id_and_thread_ts(payload: dict) -> bool: """Verifies if the given payload has both channel_id and thread_ts under assistant_thread property. + This data pattern is available for assistant_* events. """ return ( diff --git a/slack_bolt/context/async_context.py b/slack_bolt/context/async_context.py index 94b2b5cbe..19577db97 100644 --- a/slack_bolt/context/async_context.py +++ b/slack_bolt/context/async_context.py @@ -145,7 +145,9 @@ async def handle_button_clicks(ack, respond): @property def complete(self) -> AsyncComplete: - """`complete()` function for this request. Once a custom function's state is set to complete, + """`complete()` function for this request. + + Once a custom function's state is set to complete, any outputs the function returns will be passed along to the next step of its housing workflow, or complete the workflow if the function is the last step in a workflow. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable. @@ -169,7 +171,9 @@ async def handle_button_clicks(context): @property def fail(self) -> AsyncFail: - """`fail()` function for this request. Once a custom function's state is set to error, + """`fail()` function for this request. + + Once a custom function's state is set to error, its housing workflow will be interrupted and any provided error message will be passed on to the end user through SlackBot. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable. diff --git a/slack_bolt/context/base_context.py b/slack_bolt/context/base_context.py index 502febcb8..c9800aede 100644 --- a/slack_bolt/context/base_context.py +++ b/slack_bolt/context/base_context.py @@ -85,6 +85,7 @@ def user_id(self) -> Optional[str]: @property def actor_enterprise_id(self) -> Optional[str]: """The action's actor's Enterprise Grid organization ID. + Note that this property is especially useful for handling events in Slack Connect channels. That being said, it's not guaranteed to have a valid ID for all events due to server-side inconsistency. """ @@ -93,6 +94,7 @@ def actor_enterprise_id(self) -> Optional[str]: @property def actor_team_id(self) -> Optional[str]: """The action's actor's workspace ID. + Note that this property is especially useful for handling events in Slack Connect channels. That being said, it's not guaranteed to have a valid ID for all events due to server-side inconsistency. """ @@ -101,6 +103,7 @@ def actor_team_id(self) -> Optional[str]: @property def actor_user_id(self) -> Optional[str]: """The action's actor's user ID. + Note that this property is especially useful for handling events in Slack Connect channels. That being said, it's not guaranteed to have a valid ID for all events due to server-side inconsistency. """ @@ -123,12 +126,13 @@ def response_url(self) -> Optional[str]: @property def matches(self) -> Optional[Tuple]: - """Returns all the matched parts in message listener's regexp""" + """Returns all the matched parts in message listener's regexp.""" return self.get("matches") @property def function_execution_id(self) -> Optional[str]: """The `function_execution_id` associated with this request. + Only available for `function_executed` and interactivity events scoped to a custom step. """ return self.get("function_execution_id") @@ -136,6 +140,7 @@ def function_execution_id(self) -> Optional[str]: @property def inputs(self) -> Optional[Dict[str, Any]]: """The `inputs` associated with this request. + Only available for `function_executed` and interactivity events scoped to a custom step. """ return self.get("inputs") @@ -150,6 +155,7 @@ def authorize_result(self) -> Optional[AuthorizeResult]: @property def function_bot_access_token(self) -> Optional[str]: """The bot token resolved for this function request. + Only available for `function_executed` and interactivity events scoped to a custom step. """ return self.get("function_bot_access_token") diff --git a/slack_bolt/context/context.py b/slack_bolt/context/context.py index b101460a5..661e9cc94 100644 --- a/slack_bolt/context/context.py +++ b/slack_bolt/context/context.py @@ -146,7 +146,9 @@ def handle_button_clicks(ack, respond): @property def complete(self) -> Complete: - """`complete()` function for this request. Once a custom function's state is set to complete, + """`complete()` function for this request. + + Once a custom function's state is set to complete, any outputs the function returns will be passed along to the next step of its housing workflow, or complete the workflow if the function is the last step in a workflow. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable. @@ -170,7 +172,9 @@ def handle_button_clicks(context): @property def fail(self) -> Fail: - """`fail()` function for this request. Once a custom function's state is set to error, + """`fail()` function for this request. + + Once a custom function's state is set to error, its housing workflow will be interrupted and any provided error message will be passed on to the end user through SlackBot. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable. diff --git a/slack_bolt/error/__init__.py b/slack_bolt/error/__init__.py index 19716cd74..baa31565c 100644 --- a/slack_bolt/error/__init__.py +++ b/slack_bolt/error/__init__.py @@ -4,7 +4,7 @@ class BoltError(Exception): - """General class in a Bolt app""" + """General class in a Bolt app.""" class BoltUnhandledRequestError(BoltError): diff --git a/slack_bolt/kwargs_injection/args.py b/slack_bolt/kwargs_injection/args.py index f2b4099d6..1c215743d 100644 --- a/slack_bolt/kwargs_injection/args.py +++ b/slack_bolt/kwargs_injection/args.py @@ -21,6 +21,7 @@ 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") diff --git a/slack_bolt/kwargs_injection/async_args.py b/slack_bolt/kwargs_injection/async_args.py index 2217cfe9f..3c1806b06 100644 --- a/slack_bolt/kwargs_injection/async_args.py +++ b/slack_bolt/kwargs_injection/async_args.py @@ -20,6 +20,7 @@ class AsyncArgs: """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") diff --git a/slack_bolt/listener/__init__.py b/slack_bolt/listener/__init__.py index a12a2b821..6b501ddae 100644 --- a/slack_bolt/listener/__init__.py +++ b/slack_bolt/listener/__init__.py @@ -1,6 +1,7 @@ -"""Listeners process an incoming request from Slack if the request's type or data structure matches -the predefined conditions of the listener. Typically, a listener acknowledge requests from Slack, -process the request data, and may send response back to Slack. +"""Listeners process incoming requests from Slack. + +A listener runs when the request's type or data structure matches its predefined conditions. +Typically, a listener acknowledges the request, processes its data, and may send a response back to Slack. """ # Don't add async module imports here diff --git a/slack_bolt/listener/async_builtins.py b/slack_bolt/listener/async_builtins.py index 7b3bc4da7..5c9711c24 100644 --- a/slack_bolt/listener/async_builtins.py +++ b/slack_bolt/listener/async_builtins.py @@ -5,7 +5,7 @@ class AsyncTokenRevocationListeners: - """Listener functions to handle token revocation / uninstallation events""" + """Listener functions to handle token revocation / uninstallation events.""" installation_store: AsyncInstallationStore diff --git a/slack_bolt/listener/async_listener_completion_handler.py b/slack_bolt/listener/async_listener_completion_handler.py index 9fc002a91..2cd7bc41a 100644 --- a/slack_bolt/listener/async_listener_completion_handler.py +++ b/slack_bolt/listener/async_listener_completion_handler.py @@ -15,7 +15,7 @@ async def handle( request: AsyncBoltRequest, response: Optional[BoltResponse], ) -> None: - """Do something extra after the listener execution + """Do something extra after the listener execution. Args: request: The request. diff --git a/slack_bolt/listener/async_listener_start_handler.py b/slack_bolt/listener/async_listener_start_handler.py index b7b10e9e7..d244fe968 100644 --- a/slack_bolt/listener/async_listener_start_handler.py +++ b/slack_bolt/listener/async_listener_start_handler.py @@ -15,7 +15,7 @@ async def handle( request: AsyncBoltRequest, response: Optional[BoltResponse], ) -> None: - """Do something extra before the listener execution + """Do something extra before the listener execution. Args: request: The request. diff --git a/slack_bolt/listener/builtins.py b/slack_bolt/listener/builtins.py index ee5891f27..e6ff7b37d 100644 --- a/slack_bolt/listener/builtins.py +++ b/slack_bolt/listener/builtins.py @@ -3,7 +3,7 @@ class TokenRevocationListeners: - """Listener functions to handle token revocation / uninstallation events""" + """Listener functions to handle token revocation / uninstallation events.""" installation_store: InstallationStore diff --git a/slack_bolt/listener/listener_completion_handler.py b/slack_bolt/listener/listener_completion_handler.py index b2c08a205..454ee52d0 100644 --- a/slack_bolt/listener/listener_completion_handler.py +++ b/slack_bolt/listener/listener_completion_handler.py @@ -15,7 +15,7 @@ def handle( request: BoltRequest, response: Optional[BoltResponse], ) -> None: - """Do something extra after the listener execution + """Do something extra after the listener execution. Args: request: The request. diff --git a/slack_bolt/listener_matcher/__init__.py b/slack_bolt/listener_matcher/__init__.py index 26f164ba6..85c89c7bc 100644 --- a/slack_bolt/listener_matcher/__init__.py +++ b/slack_bolt/listener_matcher/__init__.py @@ -1,4 +1,5 @@ """A listener matcher is a simplified version of listener middleware. + A listener matcher function returns bool value instead of `next()` method invocation inside. This interface enables developers to utilize simple predicate functions for additional listener conditions. """ diff --git a/slack_bolt/middleware/__init__.py b/slack_bolt/middleware/__init__.py index c28ffd78d..c62cd6650 100644 --- a/slack_bolt/middleware/__init__.py +++ b/slack_bolt/middleware/__init__.py @@ -1,5 +1,6 @@ -"""A middleware processes request data and calls `next()` method -if the execution chain should continue running the following middleware. +"""A middleware processes request data and controls the execution chain. + +Call the `next()` method if the execution chain should continue running the following middleware. Middleware can be used globally before all listener executions. It's also possible to run a middleware only for a particular listener. diff --git a/slack_bolt/middleware/async_middleware.py b/slack_bolt/middleware/async_middleware.py index 163def40a..0283bb3e8 100644 --- a/slack_bolt/middleware/async_middleware.py +++ b/slack_bolt/middleware/async_middleware.py @@ -20,6 +20,7 @@ async def async_process( next: Callable[[], Awaitable[BoltResponse]], ) -> Optional[BoltResponse]: """Processes a request data before other middleware and listeners. + A middleware calls `next()` function if the chain should continue. @app.middleware @@ -47,5 +48,5 @@ async def simple_middleware(req, resp, next_): @property def name(self) -> str: - """The name of this middleware""" + """The name of this middleware.""" return f"{self.__module__}.{self.__class__.__name__}" diff --git a/slack_bolt/middleware/authorization/single_team_authorization.py b/slack_bolt/middleware/authorization/single_team_authorization.py index c2bc1488c..21fb115e1 100644 --- a/slack_bolt/middleware/authorization/single_team_authorization.py +++ b/slack_bolt/middleware/authorization/single_team_authorization.py @@ -30,6 +30,7 @@ def __init__( Args: auth_test_result: The initial `auth.test` API call result. base_logger: The base logger + user_facing_authorize_error_message: The message shown to the end-user when authorization fails """ self.auth_test_result = auth_test_result self.logger = get_bolt_logger(SingleTeamAuthorization, base_logger=base_logger) diff --git a/slack_bolt/middleware/middleware.py b/slack_bolt/middleware/middleware.py index 560499d6c..2a566a5b8 100644 --- a/slack_bolt/middleware/middleware.py +++ b/slack_bolt/middleware/middleware.py @@ -20,6 +20,7 @@ def process( next: Callable[[], BoltResponse], ) -> Optional[BoltResponse]: """Processes a request data before other middleware and listeners. + A middleware calls `next()` function if the chain should continue. @app.middleware @@ -47,5 +48,5 @@ def simple_middleware(req, resp, next_): @property def name(self) -> str: - """The name of this middleware""" + """The name of this middleware.""" return f"{self.__module__}.{self.__class__.__name__}" diff --git a/slack_bolt/middleware/request_verification/async_request_verification.py b/slack_bolt/middleware/request_verification/async_request_verification.py index 3fb9e209b..99b86a63c 100644 --- a/slack_bolt/middleware/request_verification/async_request_verification.py +++ b/slack_bolt/middleware/request_verification/async_request_verification.py @@ -7,8 +7,9 @@ class AsyncRequestVerification(RequestVerification, AsyncMiddleware): - """Verifies an incoming request by checking the validity of - `x-slack-signature`, `x-slack-request-timestamp`, and its body data. + """Verifies an incoming request from Slack. + + Checks the validity of `x-slack-signature`, `x-slack-request-timestamp`, and the request body data. Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. """ diff --git a/slack_bolt/middleware/request_verification/request_verification.py b/slack_bolt/middleware/request_verification/request_verification.py index 82d1b72e1..1425f7baf 100644 --- a/slack_bolt/middleware/request_verification/request_verification.py +++ b/slack_bolt/middleware/request_verification/request_verification.py @@ -11,8 +11,9 @@ class RequestVerification(Middleware): def __init__(self, signing_secret: str, base_logger: Optional[Logger] = None): - """Verifies an incoming request by checking the validity of - `x-slack-signature`, `x-slack-request-timestamp`, and its body data. + """Verifies an incoming request from Slack. + + Checks the validity of `x-slack-signature`, `x-slack-request-timestamp`, and the request body data. Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. diff --git a/slack_bolt/middleware/ssl_check/ssl_check.py b/slack_bolt/middleware/ssl_check/ssl_check.py index 88c5105ef..5e5d9ad9c 100644 --- a/slack_bolt/middleware/ssl_check/ssl_check.py +++ b/slack_bolt/middleware/ssl_check/ssl_check.py @@ -17,6 +17,7 @@ def __init__( base_logger: Optional[Logger] = None, ): """Handles `ssl_check` requests. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details. Args: diff --git a/slack_bolt/request/async_request.py b/slack_bolt/request/async_request.py index 73891446e..c6a06f309 100644 --- a/slack_bolt/request/async_request.py +++ b/slack_bolt/request/async_request.py @@ -41,7 +41,6 @@ def __init__( 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): diff --git a/slack_bolt/version.py b/slack_bolt/version.py index 2c08c0adb..bc9ad30ef 100644 --- a/slack_bolt/version.py +++ b/slack_bolt/version.py @@ -1,3 +1,3 @@ -"""Check the latest version at https://pypi.org/project/slack-bolt/""" +"""Check the latest version at https://pypi.org/project/slack-bolt/.""" __version__ = "1.30.0" diff --git a/slack_bolt/workflows/step/async_step.py b/slack_bolt/workflows/step/async_step.py index 7fa0ed858..b291fc1b2 100644 --- a/slack_bolt/workflows/step/async_step.py +++ b/slack_bolt/workflows/step/async_step.py @@ -28,7 +28,8 @@ class AsyncWorkflowStepBuilder: - """Steps from apps + """Steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ @@ -44,10 +45,9 @@ def __init__( app_name: Optional[str] = None, base_logger: Optional[Logger] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -86,10 +86,9 @@ def edit( middleware: Optional[Union[Callable, AsyncMiddleware]] = None, lazy: Optional[List[Callable[..., Awaitable[None]]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -139,10 +138,9 @@ def save( middleware: Optional[Union[Callable, AsyncMiddleware]] = None, lazy: Optional[List[Callable[..., Awaitable[None]]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -192,10 +190,9 @@ def execute( middleware: Optional[Union[Callable, AsyncMiddleware]] = None, lazy: Optional[List[Callable[..., Awaitable[None]]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -239,10 +236,9 @@ async def _wrapper(*args, **kwargs): return _inner def build(self, base_logger: Optional[Logger] = None) -> "AsyncWorkflowStep": - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -337,10 +333,9 @@ def __init__( app_name: Optional[str] = None, base_logger: Optional[Logger] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -383,10 +378,9 @@ def builder( callback_id: Union[str, Pattern], base_logger: Optional[Logger] = None, ) -> AsyncWorkflowStepBuilder: - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return AsyncWorkflowStepBuilder(callback_id, base_logger=base_logger) diff --git a/slack_bolt/workflows/step/async_step_middleware.py b/slack_bolt/workflows/step/async_step_middleware.py index 5801a51e6..09475a2cc 100644 --- a/slack_bolt/workflows/step/async_step_middleware.py +++ b/slack_bolt/workflows/step/async_step_middleware.py @@ -10,7 +10,7 @@ class AsyncWorkflowStepMiddleware(AsyncMiddleware): - """Base middleware for step from app specific ones""" + """Base middleware for step from app specific ones.""" def __init__(self, step: AsyncWorkflowStep): self.step = step diff --git a/slack_bolt/workflows/step/step.py b/slack_bolt/workflows/step/step.py index 4fca25717..57cab9bcd 100644 --- a/slack_bolt/workflows/step/step.py +++ b/slack_bolt/workflows/step/step.py @@ -23,7 +23,8 @@ class WorkflowStepBuilder: - """Steps from apps + """Steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ @@ -39,10 +40,9 @@ def __init__( app_name: Optional[str] = None, base_logger: Optional[Logger] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -81,10 +81,9 @@ def edit( middleware: Optional[Union[Callable, Middleware]] = None, lazy: Optional[List[Callable[..., None]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -110,7 +109,6 @@ def edit_my_step(ack, configure): middleware: Listener middleware lazy: Lazy listeners """ - if _is_used_without_argument(args): func = args[0] self._edit = self._to_listener("edit", func, matchers, middleware) @@ -135,10 +133,9 @@ def save( middleware: Optional[Union[Callable, Middleware]] = None, lazy: Optional[List[Callable[..., None]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -188,10 +185,9 @@ def execute( middleware: Optional[Union[Callable, Middleware]] = None, lazy: Optional[List[Callable[..., None]]] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -235,10 +231,9 @@ def _wrapper(*args, **kwargs): return _inner def build(self, base_logger: Optional[Logger] = None) -> "WorkflowStep": - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -348,10 +343,9 @@ def __init__( app_name: Optional[str] = None, base_logger: Optional[Logger] = None, ): - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -390,10 +384,9 @@ def __init__( @classmethod def builder(cls, callback_id: Union[str, Pattern], base_logger: Optional[Logger] = None) -> WorkflowStepBuilder: - """ - Deprecated: - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + """Deprecated: Steps from apps for legacy workflows are now deprecated. + + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return WorkflowStepBuilder( callback_id, diff --git a/slack_bolt/workflows/step/step_middleware.py b/slack_bolt/workflows/step/step_middleware.py index 59af001a7..ff0616e6e 100644 --- a/slack_bolt/workflows/step/step_middleware.py +++ b/slack_bolt/workflows/step/step_middleware.py @@ -10,7 +10,7 @@ class WorkflowStepMiddleware(Middleware): - """Base middleware for step from app specific ones""" + """Base middleware for step from app specific ones.""" def __init__(self, step: WorkflowStep): self.step = step diff --git a/tests/slack_bolt/logger/test_unmatched_suggestions.py b/tests/slack_bolt/logger/test_unmatched_suggestions.py index 426f8fd72..2c0c82b99 100644 --- a/tests/slack_bolt/logger/test_unmatched_suggestions.py +++ b/tests/slack_bolt/logger/test_unmatched_suggestions.py @@ -22,7 +22,8 @@ def test_block_actions(self): "block_id": "b", "action_id": "action-id-value", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -30,7 +31,9 @@ def test_block_actions(self): def handle_some_action(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) def test_attachment_actions(self): req: BoltRequest = BoltRequest(body=attachment_actions, mode="socket_mode") @@ -46,7 +49,8 @@ def test_attachment_actions(self): } ], } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -54,7 +58,9 @@ def test_attachment_actions(self): def handle_some_action(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) def test_app_mention_event(self): req: BoltRequest = BoltRequest(body=app_mention_event, mode="socket_mode") @@ -63,14 +69,17 @@ def test_app_mention_event(self): "event": {"type": "app_mention"}, } message = warning_unhandled_request(req) - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.event("app_mention") def handle_app_mention_events(body, logger): logger.info(body) -""" == message +""" + == message + ) def test_function_event(self): req: BoltRequest = BoltRequest(body=function_event, mode="socket_mode") @@ -79,7 +88,8 @@ def test_function_event(self): "event": {"type": "function_executed"}, } message = warning_unhandled_request(req) - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -94,7 +104,9 @@ def handle_some_function(ack, body, complete, fail, logger): except Exception as e: error = f"Failed to handle a function request (error: {{e}})" fail(error=error) -""" == message +""" + == message + ) def test_commands(self): req: BoltRequest = BoltRequest(body=slash_command, mode="socket_mode") @@ -103,7 +115,8 @@ def test_commands(self): "type": None, "command": "/start-conv", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -111,7 +124,9 @@ def test_commands(self): def handle_some_command(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) def test_shortcut(self): req: BoltRequest = BoltRequest(body=global_shortcut, mode="socket_mode") @@ -120,7 +135,8 @@ def test_shortcut(self): "type": "shortcut", "callback_id": "test-shortcut", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -128,7 +144,9 @@ def test_shortcut(self): def handle_shortcuts(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) req: BoltRequest = BoltRequest(body=message_shortcut, mode="socket_mode") message = warning_unhandled_request(req) @@ -136,7 +154,8 @@ def handle_shortcuts(ack, body, logger): "type": "message_action", "callback_id": "test-shortcut", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -144,7 +163,9 @@ def handle_shortcuts(ack, body, logger): def handle_shortcuts(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) def test_view(self): req: BoltRequest = BoltRequest(body=view_submission, mode="socket_mode") @@ -153,7 +174,8 @@ def test_view(self): "type": "view_submission", "view": {"type": "modal", "callback_id": "view-id"}, } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -161,7 +183,9 @@ def test_view(self): def handle_view_submission_events(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) req: BoltRequest = BoltRequest(body=view_closed, mode="socket_mode") message = warning_unhandled_request(req) @@ -169,7 +193,8 @@ def handle_view_submission_events(ack, body, logger): "type": "view_closed", "view": {"type": "modal", "callback_id": "view-id"}, } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -177,7 +202,9 @@ def handle_view_submission_events(ack, body, logger): def handle_view_closed_events(ack, body, logger): ack() logger.info(body) -""" == message +""" + == message + ) def test_block_suggestion(self): req: BoltRequest = BoltRequest(body=block_suggestion, mode="socket_mode") @@ -189,14 +216,17 @@ def test_block_suggestion(self): "action_id": "the-id", "value": "search word", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options("the-id") def handle_some_options(ack): ack(options=[ ... ]) -""" == message +""" + == message + ) def test_dialog_suggestion(self): req: BoltRequest = BoltRequest(body=dialog_suggestion, mode="socket_mode") @@ -206,14 +236,17 @@ def test_dialog_suggestion(self): "callback_id": "the-id", "value": "search keyword", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options({{"type": "dialog_suggestion", "callback_id": "the-id"}}) def handle_some_options(ack): ack(options=[ ... ]) -""" == message +""" + == message + ) def test_step(self): req: BoltRequest = BoltRequest(body=step_edit_payload, mode="socket_mode") @@ -222,7 +255,8 @@ def test_step(self): "type": "workflow_step_edit", "callback_id": "copy_review", } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -235,14 +269,17 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" == message +""" + == message + ) req: BoltRequest = BoltRequest(body=step_save_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "view_submission", "view": {"type": "workflow_step", "callback_id": "copy_review"}, } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -255,14 +292,17 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" == message +""" + == message + ) req: BoltRequest = BoltRequest(body=step_execute_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "event_callback", "event": {"type": "workflow_step_execute"}, } - assert f"""Unhandled request ({filtered_body}) + assert ( + f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -275,7 +315,9 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" == message +""" + == message + ) block_actions = { @@ -707,9 +749,7 @@ def test_step(self): ], "private_metadata": "This is for you!", "callback_id": "view-id", - "state": { - "values": {"hspI": {"maBWU": {"type": "plain_text_input", "value": "test"}}} - }, + "state": {"values": {"hspI": {"maBWU": {"type": "plain_text_input", "value": "test"}}}}, "hash": "1596530361.3wRYuk3R", "title": { "type": "plain_text", From 410b24587e052f2cfc83fc07b346359fe5e21176 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 27 Aug 2026 17:04:10 -0400 Subject: [PATCH 2/4] fix: correct docstring content defects left by the pydocstyle sweep The ruff pydocstyle (D) reformatting enforced docstring shape but not content, leaving six correctness/consistency defects: - falcon/resource.py: reformatting turned the usage snippet into an invalid example (`app = App().`); restore a proper summary + clean code - socket_mode base_handler.py / async_base_handler.py: summary split mid sentence left a dangling lowercase fragment; merge it back - __init__.py: "features.Read" was missing a space (shown on PyPI) - async_app.py dialog_cancellation: summary wrongly said dialog_submission - app.py / async_app.py step(): de-indent the orphaned Deprecated block - async_authorize.py: add backticks around `authorize` to mirror the sync CallableAuthorize docstring Docstring-only; no behavior change. Sync/async pairs kept mirrored. Co-Authored-By: Claude --- slack_bolt/__init__.py | 2 +- slack_bolt/adapter/falcon/resource.py | 5 +++-- slack_bolt/adapter/socket_mode/async_base_handler.py | 3 +-- slack_bolt/adapter/socket_mode/base_handler.py | 3 +-- slack_bolt/app/app.py | 4 ++-- slack_bolt/app/async_app.py | 6 +++--- slack_bolt/authorization/async_authorize.py | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/slack_bolt/__init__.py b/slack_bolt/__init__.py index 038fde4a8..73336725b 100644 --- a/slack_bolt/__init__.py +++ b/slack_bolt/__init__.py @@ -1,4 +1,4 @@ -"""A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/creating-an-app) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. +"""A Python framework to build Slack apps in a flash with the latest platform features. Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/creating-an-app) 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://docs.slack.dev/tools/bolt-python/ * GitHub repository: https://github.com/slackapi/bolt-python diff --git a/slack_bolt/adapter/falcon/resource.py b/slack_bolt/adapter/falcon/resource.py index 66e75d818..f80338755 100644 --- a/slack_bolt/adapter/falcon/resource.py +++ b/slack_bolt/adapter/falcon/resource.py @@ -11,9 +11,10 @@ class SlackAppResource: - """from slack_bolt import App. + """For use with WSGI Falcon Apps. - app = App(). + from slack_bolt import App + app = App() import falcon api = application = falcon.API() diff --git a/slack_bolt/adapter/socket_mode/async_base_handler.py b/slack_bolt/adapter/socket_mode/async_base_handler.py index aeb41d5a2..2a8355ba4 100644 --- a/slack_bolt/adapter/socket_mode/async_base_handler.py +++ b/slack_bolt/adapter/socket_mode/async_base_handler.py @@ -38,9 +38,8 @@ async def close_async(self): await self.client.close() async def start_async(self): - """Establishes a new connection and then starts infinite sleep. + """Establishes a new connection and then starts infinite sleep to prevent the termination of this process. - to prevent the termination of this process. If you don't want to have the sleep, use `#connect()` method instead. """ await self.connect_async() diff --git a/slack_bolt/adapter/socket_mode/base_handler.py b/slack_bolt/adapter/socket_mode/base_handler.py index 6b36ce014..cf74fed2c 100644 --- a/slack_bolt/adapter/socket_mode/base_handler.py +++ b/slack_bolt/adapter/socket_mode/base_handler.py @@ -41,9 +41,8 @@ def close(self): self.client.close() def start(self): - """Establishes a new connection and then blocks the current thread. + """Establishes a new connection and then blocks the current thread to prevent the termination of this process. - to prevent the termination of this process. If you don't want to block the current thread, use `#connect()` method instead. """ self.connect() diff --git a/slack_bolt/app/app.py b/slack_bolt/app/app.py index 2b309db4b..cbaa340f7 100644 --- a/slack_bolt/app/app.py +++ b/slack_bolt/app/app.py @@ -720,8 +720,8 @@ def step( ): """Deprecated: register a new step from app listener. - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + Steps from apps for legacy workflows are now deprecated. + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. diff --git a/slack_bolt/app/async_app.py b/slack_bolt/app/async_app.py index ebe7e315a..729d626c2 100644 --- a/slack_bolt/app/async_app.py +++ b/slack_bolt/app/async_app.py @@ -745,8 +745,8 @@ def step( ): """Deprecated: register a new step from app listener. - Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ + Steps from apps for legacy workflows are now deprecated. + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -1225,7 +1225,7 @@ def dialog_cancellation( matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: - """Registers a new `dialog_submission` listener. + """Registers a new `dialog_cancellation` listener. Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details. """ diff --git a/slack_bolt/authorization/async_authorize.py b/slack_bolt/authorization/async_authorize.py index e1aece4fd..aa61a209d 100644 --- a/slack_bolt/authorization/async_authorize.py +++ b/slack_bolt/authorization/async_authorize.py @@ -38,7 +38,7 @@ async def __call__( class AsyncCallableAuthorize(AsyncAuthorize): - """When you pass the authorize argument in AsyncApp constructor, this authorize implementation will be used.""" + """When you pass the `authorize` argument in AsyncApp constructor, this `authorize` implementation will be used.""" def __init__(self, *, logger: Logger, func: Callable[..., Awaitable[AuthorizeResult]]): self.logger = logger From bd9a3029fac9691b80cf152cc0f2bf5e5aaa0652 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 27 Aug 2026 17:12:38 -0400 Subject: [PATCH 3/4] chore: trim the pydocstyle convention comment to the essential caveat Drop the restatement of what the google convention does (discoverable in ruff's docs) and keep only the non-obvious footgun: switching the bare `select = ["D"]` category to explicit D codes overrides the convention. Co-Authored-By: Claude --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7110dff4d..bf43e3d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,9 +59,7 @@ ignore = [ ] [tool.ruff.lint.pydocstyle] -# google convention resolves D203/D211 and D212/D213 conflicts and implicitly ignores -# D400/D401/D404/D413. Keep `select = ["D"]` a category select -- an explicit rule-code -# select would override the convention and resurface those rules. +# Keep `select = ["D"]` a bare category select; listing explicit D codes would override this convention. convention = "google" [tool.ruff.format] From a5430d0b3a948a087da5a7cc9c45148f25011c95 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 27 Aug 2026 19:21:25 -0400 Subject: [PATCH 4/4] chore: fix stale ruff-format entry in .git-blame-ignore-revs The listed 07e8ac9d was the pre-squash branch commit from PR #1566; that SHA is unreachable from main, so git blame silently ignored the entry. Point it at the squash-merge commit 5814077e, which is the reachable commit that actually carried the ruff-format sweep onto main. Co-Authored-By: Claude --- .git-blame-ignore-revs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 08e355c7f..a7be4ecba 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,5 +1,5 @@ # change black settings 0e4cd56b69e8f83166cd262f762802b7f18c3d21 -# apply ruff format across the codebase -07e8ac9d98c535ade20040883a04734b4c9d04b8 +# apply ruff format across the codebase (#1566) +5814077ee7ef79eaa4df6e4602260c179536a814