From 406a8e343cbb13d89e4370afc5bb6d15f8cb86bf Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 27 May 2026 12:01:09 -0600 Subject: [PATCH 1/2] feat(web): expose public ts property on ChatStream for fallback recovery --- slack_sdk/web/async_chat_stream.py | 9 +++++++++ slack_sdk/web/chat_stream.py | 9 +++++++++ tests/slack_sdk/web/test_chat_stream.py | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py index a527c41e1..20e3fd98b 100644 --- a/slack_sdk/web/async_chat_stream.py +++ b/slack_sdk/web/async_chat_stream.py @@ -86,6 +86,15 @@ def __init__( self._stream_ts: Optional[str] = None self._buffer_size = buffer_size + @property + def ts(self) -> Optional[str]: + """The message timestamp of the stream. + + Returns None until the first flush (when chat.startStream is called). + Can be used with chat.update as a fallback if the stream expires server-side. + """ + return self._stream_ts + async def append( self, *, diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py index 7bd2daec9..9f97ac7cc 100644 --- a/slack_sdk/web/chat_stream.py +++ b/slack_sdk/web/chat_stream.py @@ -76,6 +76,15 @@ def __init__( self._stream_ts: Optional[str] = None self._buffer_size = buffer_size + @property + def ts(self) -> Optional[str]: + """The message timestamp of the stream. + + Returns None until the first flush (when chat.startStream is called). + Can be used with chat.update as a fallback if the stream expires server-side. + """ + return self._stream_ts + def append( self, *, diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py index 2db5dff88..841de9c38 100644 --- a/tests/slack_sdk/web/test_chat_stream.py +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -262,6 +262,22 @@ def test_streams_with_authorship_args(self): self.assertEqual(start_request.get("icon_emoji"), "abacus") self.assertNotIn("icon_url", start_request) + def test_ts_property_is_none_before_flush_and_set_after(self): + streamer = self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + self.assertIsNone(streamer.ts) + + streamer.append(markdown_text="hello!") + self.assertEqual(streamer.ts, "123.123") + + streamer.stop() + self.assertEqual(streamer.ts, "123.123") + def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = self.client.chat_stream( channel="C0123456789", From 6ddc8d348b24866b50d7508eeb914b6f31b24cac Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Fri, 29 May 2026 14:15:18 -0600 Subject: [PATCH 2/2] add async tests --- .../web/test_async_chat_stream.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/slack_sdk_async/web/test_async_chat_stream.py b/tests/slack_sdk_async/web/test_async_chat_stream.py index 2a4f5b931..5d5ccf344 100644 --- a/tests/slack_sdk_async/web/test_async_chat_stream.py +++ b/tests/slack_sdk_async/web/test_async_chat_stream.py @@ -244,6 +244,23 @@ async def test_streams_a_chunk_message(self): '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', ) + @async_test + async def test_ts_property_is_none_before_flush_and_set_after(self): + streamer = await self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + self.assertIsNone(streamer.ts) + + await streamer.append(markdown_text="hello!") + self.assertEqual(streamer.ts, "123.123") + + await streamer.stop() + self.assertEqual(streamer.ts, "123.123") + @async_test async def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = await self.client.chat_stream(