Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions slack_bolt/context/assistant/assistant_utilities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Optional

from slack_sdk.web import WebClient
Expand Down Expand Up @@ -51,6 +52,13 @@ def is_valid(self) -> bool:

@property
def set_status(self) -> SetStatus:
warnings.warn(
"AssistantUtilities.set_status is deprecated. "
"Use the set_status argument directly in your listener function "
"or access it via context.set_status instead.",
DeprecationWarning,
stacklevel=2,
)
return SetStatus(self.client, self.channel_id, self.thread_ts)
Comment on lines 53 to 62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📣 question: This deprecation is visible to callers of set_status using the assistant class? I think this has solid recommendations but want to make sure I'm not misunderstanding the scope of change!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope from what I understand this deprecation would only be visible for users that try to do something like

assistant_utils = AssistantUtilities()

assistant_utils.set_status

I'm actually considering removing def set_status(self) entirely since I think this class was intended for internal use 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WilliamBergamin Ahha this catches confusion I had in not finding these messages! Let's save removal for upcoming breaking change since I understand type definitions might be imported from a particular path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's save removal for upcoming breaking change since I understand type definitions might be imported from a particular path?

Yess I agree it will be safer to do this in a major


@property
Expand Down
8 changes: 8 additions & 0 deletions slack_bolt/context/assistant/async_assistant_utilities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Optional

from slack_sdk.web.async_client import AsyncWebClient
Expand Down Expand Up @@ -54,6 +55,13 @@ def is_valid(self) -> bool:

@property
def set_status(self) -> AsyncSetStatus:
warnings.warn(
"AsyncAssistantUtilities.set_status is deprecated. "
"Use the set_status argument directly in your listener function "
"or access it via context.set_status instead.",
DeprecationWarning,
stacklevel=2,
)
return AsyncSetStatus(self.client, self.channel_id, self.thread_ts)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from slack_bolt.context.assistant.async_assistant_utilities import AsyncAssistantUtilities
from slack_bolt.context.assistant.thread_context_store.async_store import AsyncAssistantThreadContextStore
from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
from slack_bolt.middleware.async_middleware import AsyncMiddleware
from slack_bolt.request.async_request import AsyncBoltRequest
from slack_bolt.request.payload_utils import is_assistant_event, to_event
Expand Down Expand Up @@ -32,7 +33,6 @@ async def async_process(
thread_context_store=self.thread_context_store,
)
req.context["say"] = assistant.say
req.context["set_status"] = assistant.set_status
req.context["set_title"] = assistant.set_title
req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
req.context["get_thread_context"] = assistant.get_thread_context
Expand All @@ -41,6 +41,11 @@ async def async_process(
# TODO: in the future we might want to introduce a "proper" extract_ts utility
thread_ts = req.context.thread_ts or event.get("ts")
if req.context.channel_id and thread_ts:
req.context["set_status"] = AsyncSetStatus(
client=req.context.client,
channel_id=req.context.channel_id,
thread_ts=thread_ts,
)
req.context["say_stream"] = AsyncSayStream(
client=req.context.client,
channel=req.context.channel_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from slack_bolt.context.assistant.assistant_utilities import AssistantUtilities
from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore
from slack_bolt.context.say_stream.say_stream import SayStream
from slack_bolt.context.set_status.set_status import SetStatus
from slack_bolt.middleware import Middleware
from slack_bolt.request.payload_utils import is_assistant_event, to_event
from slack_bolt.request.request import BoltRequest
Expand All @@ -26,7 +27,6 @@ def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], Bo
thread_context_store=self.thread_context_store,
)
req.context["say"] = assistant.say
req.context["set_status"] = assistant.set_status
req.context["set_title"] = assistant.set_title
req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
req.context["get_thread_context"] = assistant.get_thread_context
Expand All @@ -35,6 +35,11 @@ def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], Bo
# TODO: in the future we might want to introduce a "proper" extract_ts utility
thread_ts = req.context.thread_ts or event.get("ts")
if req.context.channel_id and thread_ts:
req.context["set_status"] = SetStatus(
client=req.context.client,
channel_id=req.context.channel_id,
thread_ts=thread_ts,
)
req.context["say_stream"] = SayStream(
client=req.context.client,
channel=req.context.channel_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down Expand Up @@ -208,7 +208,7 @@ def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down Expand Up @@ -236,7 +236,7 @@ def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down
171 changes: 171 additions & 0 deletions tests/scenario_tests/test_events_set_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import json
from threading import Event
from urllib.parse import quote

from slack_sdk.web import WebClient

from slack_bolt import App, BoltContext, BoltRequest
from slack_bolt.context.set_status.set_status import SetStatus
from slack_bolt.middleware.assistant import Assistant
from tests.mock_web_api_server import (
assert_auth_test_count,
assert_received_request_count,
cleanup_mock_web_api_server,
setup_mock_web_api_server,
)
from tests.scenario_tests.test_app import app_mention_event_body
from tests.scenario_tests.test_events_assistant import thread_started_event_body
from tests.scenario_tests.test_events_assistant import user_message_event_body as threaded_user_message_event_body
from tests.scenario_tests.test_message_bot import bot_message_event_payload, user_message_event_payload
from tests.scenario_tests.test_view_submission import body as view_submission_body
from tests.utils import remove_os_env_temporarily, restore_os_env


class TestEventsSetStatus:
valid_token = "xoxb-valid"
mock_api_server_base_url = "http://localhost:8888"
web_client = WebClient(
token=valid_token,
base_url=mock_api_server_base_url,
)

def setup_method(self):
self.old_os_env = remove_os_env_temporarily()
setup_mock_web_api_server(self)

def teardown_method(self):
cleanup_mock_web_api_server(self)
restore_os_env(self.old_os_env)

def test_set_status_injected_for_app_mention(self):
app = App(client=self.web_client)

@app.event("app_mention")
def handle_mention(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "C111"
assert set_status.thread_ts == "1595926230.009600"
set_status(status="Thinking...")

request = BoltRequest(body=app_mention_event_body, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_injected_for_threaded_message(self):
app = App(client=self.web_client)

@app.event("message")
def handle_message(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "D111"
assert set_status.thread_ts == "1726133698.626339"
set_status(status="Thinking...")

request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_in_user_message(self):
app = App(client=self.web_client)

@app.message("")
def handle_user_message(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "C111"
assert set_status.thread_ts == "1610261659.001400"
set_status(status="Thinking...")

request = BoltRequest(body=user_message_event_payload, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_in_bot_message(self):
app = App(client=self.web_client)

@app.message("")
def handle_bot_message(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "C111"
assert set_status.thread_ts == "1610261539.000900"
set_status(status="Thinking...")

request = BoltRequest(body=bot_message_event_payload, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_in_assistant_thread_started(self):
app = App(client=self.web_client)
assistant = Assistant()

@assistant.thread_started
def start_thread(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "D111"
assert set_status.thread_ts == "1726133698.626339"
set_status(status="Thinking...")

app.assistant(assistant)

request = BoltRequest(body=thread_started_event_body, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_in_assistant_user_message(self):
app = App(client=self.web_client)
assistant = Assistant()

@assistant.user_message
def handle_user_message(set_status: SetStatus, context: BoltContext):
assert set_status is not None
assert isinstance(set_status, SetStatus)
assert set_status == context.set_status
assert set_status.channel_id == "D111"
assert set_status.thread_ts == "1726133698.626339"
set_status(status="Thinking...")

app.assistant(assistant)

request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1)

def test_set_status_is_none_for_view_submission(self):
app = App(client=self.web_client, request_verification_enabled=False)
listener_called = Event()

@app.view("view-id")
def handle_view(ack, set_status, context: BoltContext):
ack()
assert set_status is None
assert context.set_status is None
listener_called.set()

request = BoltRequest(
body=f"payload={quote(json.dumps(view_submission_body))}",
)
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
assert listener_called.is_set()
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ async def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down Expand Up @@ -226,7 +226,7 @@ async def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down Expand Up @@ -255,7 +255,7 @@ async def handle_message_event(
):
assert context.thread_ts == "1726133698.626339"
assert say.thread_ts == None
assert set_status is None
assert set_status is not None
assert set_title is None
assert set_suggested_prompts is None
assert get_thread_context is None
Expand Down
Loading
Loading