diff --git a/docs/reference/oauth/callback_options.html b/docs/reference/oauth/callback_options.html
index 7ad3734b3..c6fc81286 100644
--- a/docs/reference/oauth/callback_options.html
+++ b/docs/reference/oauth/callback_options.html
@@ -181,7 +181,7 @@ Inherited members
reason: str,
error: Optional[Exception] = None,
suggested_status_code: int,
- settings: "OAuthSettings", # type: ignore[name-defined]
+ settings: "OAuthSettings",
default: "CallbackOptions",
):
"""The arguments for a failure function.
@@ -233,7 +233,7 @@ Args
*,
request: BoltRequest,
installation: Installation,
- settings: "OAuthSettings", # type: ignore[name-defined]
+ settings: "OAuthSettings",
default: "CallbackOptions",
):
"""The arguments for a success function.
diff --git a/docs/reference/oauth/oauth_settings.html b/docs/reference/oauth/oauth_settings.html
index cd8def497..1eb2ab7dd 100644
--- a/docs/reference/oauth/oauth_settings.html
+++ b/docs/reference/oauth/oauth_settings.html
@@ -48,7 +48,7 @@
class OAuthSettings
-(*,
client_id: str | None = None,
client_secret: str | None = None,
scopes: Sequence[str] | str | None = None,
user_scopes: Sequence[str] | str | None = None,
redirect_uri: str | None = None,
install_path: str = '/slack/install',
install_page_rendering_enabled: bool = True,
redirect_uri_path: str = '/slack/oauth_redirect',
callback_options: CallbackOptions | None = None,
success_url: str | None = None,
failure_url: str | None = None,
authorization_url: str | None = None,
installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
installation_store_bot_only: bool = False,
token_rotation_expiration_minutes: int = 120,
user_token_resolution: str = 'authed_user',
state_validation_enabled: bool = True,
state_store: slack_sdk.oauth.state_store.state_store.OAuthStateStore | None = None,
state_cookie_name: str = 'slack-app-oauth-state',
state_expiration_seconds: int = 600,
logger: logging.Logger = <Logger slack_bolt.oauth.oauth_settings (WARNING)>)
+(*,
client_id: str | None = None,
client_secret: str | None = None,
scopes: str | Sequence[str] | None = None,
user_scopes: str | Sequence[str] | None = None,
redirect_uri: str | None = None,
install_path: str = '/slack/install',
install_page_rendering_enabled: bool = True,
redirect_uri_path: str = '/slack/oauth_redirect',
callback_options: CallbackOptions | None = None,
success_url: str | None = None,
failure_url: str | None = None,
authorization_url: str | None = None,
installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
installation_store_bot_only: bool = False,
token_rotation_expiration_minutes: int = 120,
user_token_resolution: str = 'authed_user',
state_validation_enabled: bool = True,
state_store: slack_sdk.oauth.state_store.state_store.OAuthStateStore | None = None,
state_cookie_name: str = 'slack-app-oauth-state',
state_expiration_seconds: int = 600,
logger: logging.Logger = <Logger slack_bolt.oauth.oauth_settings (WARNING)>)
-
diff --git a/docs/reference/request/internals.html b/docs/reference/request/internals.html
index bc13932ec..bd8319183 100644
--- a/docs/reference/request/internals.html
+++ b/docs/reference/request/internals.html
@@ -268,12 +268,12 @@
return channel.get("id")
if "channel_id" in payload:
return payload.get("channel_id")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return extract_channel_id(payload["event"])
- if payload.get("item") is not None:
+ if isinstance(payload.get("item"), dict):
# reaction_added: body["event"]["item"]
return extract_channel_id(payload["item"])
- if payload.get("assistant_thread") is not None:
+ if isinstance(payload.get("assistant_thread"), dict):
# assistant_thread_started
return extract_channel_id(payload["assistant_thread"])
return None
@@ -317,10 +317,10 @@
return extract_enterprise_id(payload["authorizations"][0])
if "enterprise_id" in payload:
return payload.get("enterprise_id")
- if payload.get("team") is not None and "enterprise_id" in payload["team"]:
+ if isinstance(payload.get("team"), dict) and "enterprise_id" in payload["team"]:
# In the case where the type is view_submission
return payload["team"].get("enterprise_id")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return extract_enterprise_id(payload["event"])
return None
@@ -337,7 +337,7 @@
def extract_function_bot_access_token(payload: Dict[str, Any]) -> Optional[str]:
if payload.get("bot_access_token") is not None:
return payload.get("bot_access_token")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return payload["event"].get("bot_access_token")
return None
@@ -354,9 +354,9 @@
def extract_function_execution_id(payload: Dict[str, Any]) -> Optional[str]:
if payload.get("function_execution_id") is not None:
return payload.get("function_execution_id")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return extract_function_execution_id(payload["event"])
- if payload.get("function_data") is not None:
+ if isinstance(payload.get("function_data"), dict):
return payload["function_data"].get("execution_id")
return None
@@ -371,9 +371,9 @@
Expand source code
def extract_function_inputs(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return payload["event"].get("inputs")
- if payload.get("function_data") is not None:
+ if isinstance(payload.get("function_data"), dict):
return payload["function_data"].get("inputs")
return None
@@ -408,13 +408,13 @@
Expand source code
def extract_team_id(payload: Dict[str, Any]) -> Optional[str]:
- app_installed_team_id = payload.get("view", {}).get("app_installed_team_id")
- if app_installed_team_id is not None:
+ view = payload.get("view")
+ if isinstance(view, dict) and view.get("app_installed_team_id") is not None:
# view_submission payloads can have `view.app_installed_team_id` when a modal view that was opened
# in a different workspace via some operations inside a Slack Connect channel.
# Note that the same for enterprise_id does not exist. When you need to know the enterprise_id as well,
# you have to run some query toward your InstallationStore to know the org where the team_id belongs to.
- return app_installed_team_id
+ return view["app_installed_team_id"]
if payload.get("team") is not None:
# With org-wide installations, payload.team in interactivity payloads can be None
# You need to extract either payload.user.team_id or payload.view.team_id as below
@@ -429,12 +429,12 @@
return extract_team_id(payload["authorizations"][0])
if "team_id" in payload:
return payload.get("team_id")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return extract_team_id(payload["event"])
- if payload.get("user") is not None:
+ if isinstance(payload.get("user"), dict):
return payload["user"]["team_id"]
- if payload.get("view") is not None:
- return payload.get("view", {})["team_id"]
+ if isinstance(payload.get("view"), dict):
+ return payload["view"]["team_id"]
return None
@@ -448,30 +448,17 @@
Expand source code
def extract_thread_ts(payload: Dict[str, Any]) -> Optional[str]:
- # This utility initially supports only the use cases for AI assistants, but it may be fine to add more patterns.
- # That said, note that thread_ts is always required for assistant threads, but it's not for channels.
- # Thus, blindly setting this thread_ts to say utility can break existing apps' behaviors.
- if is_assistant_event(payload):
- event = payload["event"]
- if (
- event.get("assistant_thread") is not None
- and event["assistant_thread"].get("channel_id") is not None
- and event["assistant_thread"].get("thread_ts") is not None
- ):
- # assistant_thread_started, assistant_thread_context_changed
- # "assistant_thread" property can exist for message event without channel_id and thread_ts
- # Thus, the above if check verifies these properties exist
- return event["assistant_thread"]["thread_ts"]
- elif event.get("channel") is not None:
- if event.get("thread_ts") is not None:
- # message in an assistant thread
- return event["thread_ts"]
- elif event.get("message", {}).get("thread_ts") is not None:
- # message_changed
- return event["message"]["thread_ts"]
- elif event.get("previous_message", {}).get("thread_ts") is not None:
- # message_deleted
- return event["previous_message"]["thread_ts"]
+ thread_ts = payload.get("thread_ts")
+ if thread_ts is not None:
+ return thread_ts
+ if isinstance(payload.get("event"), dict):
+ return extract_thread_ts(payload["event"])
+ if isinstance(payload.get("assistant_thread"), dict):
+ return extract_thread_ts(payload["assistant_thread"])
+ if isinstance(payload.get("message"), dict):
+ return extract_thread_ts(payload["message"])
+ if isinstance(payload.get("previous_message"), dict):
+ return extract_thread_ts(payload["previous_message"])
return None
@@ -493,12 +480,12 @@
return user.get("id")
if "user_id" in payload:
return payload.get("user_id")
- if payload.get("event") is not None:
+ if isinstance(payload.get("event"), dict):
return extract_user_id(payload["event"])
- if payload.get("message") is not None:
+ if isinstance(payload.get("message"), dict):
# message_changed: body["event"]["message"]
return extract_user_id(payload["message"])
- if payload.get("previous_message") is not None:
+ if isinstance(payload.get("previous_message"), dict):
# message_deleted: body["event"]["previous_message"]
return extract_user_id(payload["previous_message"])
return None
diff --git a/slack_bolt/context/say_stream/async_say_stream.py b/slack_bolt/context/say_stream/async_say_stream.py
index dc752d02a..af776891b 100644
--- a/slack_bolt/context/say_stream/async_say_stream.py
+++ b/slack_bolt/context/say_stream/async_say_stream.py
@@ -1,11 +1,8 @@
-import warnings
from typing import Optional
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_chat_stream import AsyncChatStream
-from slack_bolt.warning import ExperimentalWarning
-
class AsyncSayStream:
client: AsyncWebClient
@@ -39,16 +36,7 @@ async def __call__(
thread_ts: Optional[str] = None,
**kwargs,
) -> AsyncChatStream:
- """Starts a new chat stream with context.
-
- Warning: This is an experimental feature and may change in future versions.
- """
- warnings.warn(
- "say_stream is experimental and may change in future versions.",
- category=ExperimentalWarning,
- stacklevel=2,
- )
-
+ """Starts a new chat stream with context."""
channel = channel or self.channel
thread_ts = thread_ts or self.thread_ts
if channel is None:
diff --git a/slack_bolt/context/say_stream/say_stream.py b/slack_bolt/context/say_stream/say_stream.py
index 1e1d7985f..b6a5ca797 100644
--- a/slack_bolt/context/say_stream/say_stream.py
+++ b/slack_bolt/context/say_stream/say_stream.py
@@ -1,11 +1,8 @@
-import warnings
from typing import Optional
from slack_sdk import WebClient
from slack_sdk.web.chat_stream import ChatStream
-from slack_bolt.warning import ExperimentalWarning
-
class SayStream:
client: WebClient
@@ -39,16 +36,7 @@ def __call__(
thread_ts: Optional[str] = None,
**kwargs,
) -> ChatStream:
- """Starts a new chat stream with context.
-
- Warning: This is an experimental feature and may change in future versions.
- """
- warnings.warn(
- "say_stream is experimental and may change in future versions.",
- category=ExperimentalWarning,
- stacklevel=2,
- )
-
+ """Starts a new chat stream with context."""
channel = channel or self.channel
thread_ts = thread_ts or self.thread_ts
if channel is None:
diff --git a/slack_bolt/request/internals.py b/slack_bolt/request/internals.py
index 15d1e7367..e0863a713 100644
--- a/slack_bolt/request/internals.py
+++ b/slack_bolt/request/internals.py
@@ -112,9 +112,9 @@ def extract_team_id(payload: Dict[str, Any]) -> Optional[str]:
if isinstance(payload.get("event"), dict):
return extract_team_id(payload["event"])
if isinstance(payload.get("user"), dict):
- return payload["user"]["team_id"]
+ return payload["user"].get("team_id")
if isinstance(payload.get("view"), dict):
- return payload["view"]["team_id"]
+ return payload["view"].get("team_id")
return None
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index 9b1349aea..ebda7dafb 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/"""
-__version__ = "1.27.0"
+__version__ = "1.28.0"
diff --git a/slack_bolt/warning/__init__.py b/slack_bolt/warning/__init__.py
deleted file mode 100644
index 4991f4cd9..000000000
--- a/slack_bolt/warning/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""Bolt specific warning types."""
-
-
-class ExperimentalWarning(FutureWarning):
- """Warning for features that are still in experimental phase."""
-
- pass
diff --git a/tests/slack_bolt/context/test_say_stream.py b/tests/slack_bolt/context/test_say_stream.py
index c8f4c3a31..29d244a65 100644
--- a/tests/slack_bolt/context/test_say_stream.py
+++ b/tests/slack_bolt/context/test_say_stream.py
@@ -2,7 +2,6 @@
from slack_sdk import WebClient
from slack_bolt.context.say_stream.say_stream import SayStream
-from slack_bolt.warning import ExperimentalWarning
from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server
@@ -20,15 +19,13 @@ def teardown_method(self):
def test_missing_channel_raises(self):
say_stream = SayStream(client=self.web_client, channel=None, thread_ts="111.222")
- with pytest.warns(ExperimentalWarning):
- with pytest.raises(ValueError, match="channel"):
- say_stream()
+ with pytest.raises(ValueError, match="channel"):
+ say_stream()
def test_missing_thread_ts_raises(self):
say_stream = SayStream(client=self.web_client, channel="C111", thread_ts=None)
- with pytest.warns(ExperimentalWarning):
- with pytest.raises(ValueError, match="thread_ts"):
- say_stream()
+ with pytest.raises(ValueError, match="thread_ts"):
+ say_stream()
def test_default_params(self):
say_stream = SayStream(
@@ -92,12 +89,3 @@ def test_buffer_size_overrides(self):
"recipient_user_id": "U222",
"task_display_mode": None,
}
-
- def test_experimental_warning(self):
- say_stream = SayStream(
- client=self.web_client,
- channel="C111",
- thread_ts="111.222",
- )
- with pytest.warns(ExperimentalWarning, match="say_stream is experimental"):
- say_stream()
diff --git a/tests/slack_bolt/request/test_internals.py b/tests/slack_bolt/request/test_internals.py
index 8cccf0431..31ac35bdd 100644
--- a/tests/slack_bolt/request/test_internals.py
+++ b/tests/slack_bolt/request/test_internals.py
@@ -1253,8 +1253,10 @@ def test_extraction_functions_invalid_dict_keys(self):
invalid_payloads = {
"event": {"event": "some_event_type"},
"user": {"user": "U12345"},
+ "user_missing_team_id": {"user": {"id": "U12345"}},
"team": {"team": "T12345"},
"view": {"view": "V12345"},
+ "view_missing_team_id": {"view": {"id": "V12345"}},
"message": {"message": "some text"},
"item": {"item": "item_id"},
"function_data": {"function_data": "fd_123"},
diff --git a/tests/slack_bolt_async/context/test_async_say_stream.py b/tests/slack_bolt_async/context/test_async_say_stream.py
index fbc4c5c7e..016549bd6 100644
--- a/tests/slack_bolt_async/context/test_async_say_stream.py
+++ b/tests/slack_bolt_async/context/test_async_say_stream.py
@@ -2,7 +2,6 @@
from slack_sdk.web.async_client import AsyncWebClient
from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
-from slack_bolt.warning import ExperimentalWarning
from tests.mock_web_api_server import (
cleanup_mock_web_api_server,
setup_mock_web_api_server,
@@ -29,16 +28,14 @@ def setup_teardown(self):
@pytest.mark.asyncio
async def test_missing_channel_raises(self):
say_stream = AsyncSayStream(client=self.web_client, channel=None, thread_ts="111.222")
- with pytest.warns(ExperimentalWarning):
- with pytest.raises(ValueError, match="channel"):
- await say_stream()
+ with pytest.raises(ValueError, match="channel"):
+ await say_stream()
@pytest.mark.asyncio
async def test_missing_thread_ts_raises(self):
say_stream = AsyncSayStream(client=self.web_client, channel="C111", thread_ts=None)
- with pytest.warns(ExperimentalWarning):
- with pytest.raises(ValueError, match="thread_ts"):
- await say_stream()
+ with pytest.raises(ValueError, match="thread_ts"):
+ await say_stream()
@pytest.mark.asyncio
async def test_default_params(self):
@@ -105,13 +102,3 @@ async def test_buffer_size_overrides(self):
"recipient_user_id": "U222",
"task_display_mode": None,
}
-
- @pytest.mark.asyncio
- async def test_experimental_warning(self):
- say_stream = AsyncSayStream(
- client=self.web_client,
- channel="C111",
- thread_ts="111.222",
- )
- with pytest.warns(ExperimentalWarning, match="say_stream is experimental"):
- await say_stream()