Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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