diff --git a/docs/reference/index.html b/docs/reference/index.html index 0245eb5ff..3cfd45f3f 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -3056,6 +3056,69 @@

Classes

kwargs = _remove_none_values(kwargs) return self.api_call("chat.stopStream", json=kwargs) + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -10346,6 +10409,122 @@

Methods

Stops a streaming conversation. https://api.slack.com/methods/chat.stopStream

+
+def chat_stream(self,
*,
buffer_size: int = 256,
channel: str,
thread_ts: str,
recipient_team_id: str | None = None,
recipient_user_id: str | None = None,
**kwargs) ‑> ChatStream
+
+
+
+ +Expand source code + +
def chat_stream(
+    self,
+    *,
+    buffer_size: int = 256,
+    channel: str,
+    thread_ts: str,
+    recipient_team_id: Optional[str] = None,
+    recipient_user_id: Optional[str] = None,
+    **kwargs,
+) -> ChatStream:
+    """Stream markdown text into a conversation.
+
+    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message,
+    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
+
+    The following methods are used:
+
+    - chat.startStream: Starts a new streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
+    - chat.appendStream: Appends text to an existing streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
+    - chat.stopStream: Stops a streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
+
+    Args:
+        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
+          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
+          limits. Default: 256.
+        channel: An encoded ID that represents a channel, private group, or DM.
+        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
+          request.
+        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
+          streaming to channels.
+        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
+        **kwargs: Additional arguments passed to the underlying API calls.
+
+    Returns:
+        ChatStream instance for managing the stream
+
+    Example:
+        ```python
+        streamer = client.chat_stream(
+            channel="C0123456789",
+            thread_ts="1700000001.123456",
+            recipient_team_id="T0123456789",
+            recipient_user_id="U0123456789",
+        )
+        streamer.append(markdown_text="**hello wo")
+        streamer.append(markdown_text="rld!**")
+        streamer.stop()
+        ```
+    """
+    return ChatStream(
+        self,
+        logger=self._logger,
+        channel=channel,
+        thread_ts=thread_ts,
+        recipient_team_id=recipient_team_id,
+        recipient_user_id=recipient_user_id,
+        buffer_size=buffer_size,
+        **kwargs,
+    )
+
+

Stream markdown text into a conversation.

+

This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

+

The following methods are used:

+ +

Args

+
+
buffer_size
+
The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
+
channel
+
An encoded ID that represents a channel, private group, or DM.
+
thread_ts
+
Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
+
recipient_team_id
+
The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
+
recipient_user_id
+
The encoded ID of the user to receive the streaming text. Required when streaming to channels.
+
**kwargs
+
Additional arguments passed to the underlying API calls.
+
+

Returns

+

ChatStream instance for managing the stream

+

Example

+
streamer = client.chat_stream(
+    channel="C0123456789",
+    thread_ts="1700000001.123456",
+    recipient_team_id="T0123456789",
+    recipient_user_id="U0123456789",
+)
+streamer.append(markdown_text="**hello wo")
+streamer.append(markdown_text="rld!**")
+streamer.stop()
+
+
def chat_unfurl(self,
*,
channel: str | None = None,
ts: str | None = None,
source: str | None = None,
unfurl_id: str | None = None,
unfurls: Dict[str, Dict] | None = None,
user_auth_blocks: str | Sequence[Dict | Block] | None = None,
user_auth_message: str | None = None,
user_auth_required: bool | None = None,
user_auth_url: str | None = None,
**kwargs) ‑> SlackResponse
@@ -15323,6 +15502,7 @@

WebClientchat_scheduledMessages_list
  • chat_startStream
  • chat_stopStream
  • +
  • chat_stream
  • chat_unfurl
  • chat_update
  • conversations_acceptSharedInvite
  • diff --git a/docs/reference/web/async_chat_stream.html b/docs/reference/web/async_chat_stream.html new file mode 100644 index 000000000..ca7bf2506 --- /dev/null +++ b/docs/reference/web/async_chat_stream.html @@ -0,0 +1,506 @@ + + + + + + +slack_sdk.web.async_chat_stream API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_sdk.web.async_chat_stream

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class AsyncChatStream +(client: AsyncWebClient,
    *,
    channel: str,
    logger: logging.Logger,
    thread_ts: str,
    buffer_size: int,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs)
    +
    +
    +
    + +Expand source code + +
    class AsyncChatStream:
    +    """A helper class for streaming markdown text into a conversation using the chat streaming APIs.
    +
    +    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API
    +    methods, with automatic buffering and state management.
    +    """
    +
    +    def __init__(
    +        self,
    +        client: "AsyncWebClient",
    +        *,
    +        channel: str,
    +        logger: logging.Logger,
    +        thread_ts: str,
    +        buffer_size: int,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        **kwargs,
    +    ):
    +        """Initialize a new ChatStream instance.
    +
    +        The __init__ method creates a unique ChatStream instance that keeps track of one chat stream.
    +
    +        Args:
    +            client: The WebClient instance to use for API calls.
    +            channel: An encoded ID that represents a channel, private group, or DM.
    +            logger: A logging channel for outputs.
    +            thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
    +              request.
    +            recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
    +              streaming to channels.
    +            recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +            buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value
    +              decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +        """
    +        self._client = client
    +        self._logger = logger
    +        self._token: Optional[str] = kwargs.pop("token", None)
    +        self._stream_args = {
    +            "channel": channel,
    +            "thread_ts": thread_ts,
    +            "recipient_team_id": recipient_team_id,
    +            "recipient_user_id": recipient_user_id,
    +            **kwargs,
    +        }
    +        self._buffer = ""
    +        self._state = "starting"
    +        self._stream_ts: Optional[str] = None
    +        self._buffer_size = buffer_size
    +
    +    async def append(
    +        self,
    +        *,
    +        markdown_text: str,
    +        **kwargs,
    +    ) -> Optional[AsyncSlackResponse]:
    +        """Append to the stream.
    +
    +        The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
    +        is stopped this method cannot be called.
    +
    +        Args:
    +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +              what will be appended to the message received so far.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +
    +        Returns:
    +            AsyncSlackResponse if the buffer was flushed, None if buffering.
    +
    +        Raises:
    +            SlackRequestError: If the stream is already completed.
    +
    +        Example:
    +            ```python
    +            streamer = client.chat_stream(
    +                channel="C0123456789",
    +                thread_ts="1700000001.123456",
    +                recipient_team_id="T0123456789",
    +                recipient_user_id="U0123456789",
    +            )
    +            streamer.append(markdown_text="**hello wo")
    +            streamer.append(markdown_text="rld!**")
    +            streamer.stop()
    +            ```
    +        """
    +        if self._state == "completed":
    +            raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
    +        if kwargs.get("token"):
    +            self._token = kwargs.pop("token")
    +        self._buffer += markdown_text
    +        if len(self._buffer) >= self._buffer_size:
    +            return await self._flush_buffer(**kwargs)
    +        details = {
    +            "buffer_length": len(self._buffer),
    +            "buffer_size": self._buffer_size,
    +            "channel": self._stream_args.get("channel"),
    +            "recipient_team_id": self._stream_args.get("recipient_team_id"),
    +            "recipient_user_id": self._stream_args.get("recipient_user_id"),
    +            "thread_ts": self._stream_args.get("thread_ts"),
    +        }
    +        self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
    +        return None
    +
    +    async def stop(
    +        self,
    +        *,
    +        markdown_text: Optional[str] = None,
    +        blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
    +        metadata: Optional[Union[Dict, Metadata]] = None,
    +        **kwargs,
    +    ) -> AsyncSlackResponse:
    +        """Stop the stream and finalize the message.
    +
    +        Args:
    +            blocks: A list of blocks that will be rendered at the bottom of the finalized message.
    +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +              what will be appended to the message received so far.
    +            metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
    +              post to Slack is accessible to any app or user who is a member of that workspace.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +
    +        Returns:
    +            AsyncSlackResponse from the chat.stopStream API call.
    +
    +        Raises:
    +            SlackRequestError: If the stream is already completed.
    +
    +        Example:
    +            ```python
    +            streamer = client.chat_stream(
    +                channel="C0123456789",
    +                thread_ts="1700000001.123456",
    +                recipient_team_id="T0123456789",
    +                recipient_user_id="U0123456789",
    +            )
    +            streamer.append(markdown_text="**hello wo")
    +            streamer.append(markdown_text="rld!**")
    +            streamer.stop()
    +            ```
    +        """
    +        if self._state == "completed":
    +            raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
    +        if kwargs.get("token"):
    +            self._token = kwargs.pop("token")
    +        if markdown_text:
    +            self._buffer += markdown_text
    +        if not self._stream_ts:
    +            response = await self._client.chat_startStream(
    +                **self._stream_args,
    +                token=self._token,
    +            )
    +            if not response.get("ts"):
    +                raise e.SlackRequestError("Failed to stop stream: stream not started")
    +            self._stream_ts = str(response["ts"])
    +            self._state = "in_progress"
    +        response = await self._client.chat_stopStream(
    +            token=self._token,
    +            channel=self._stream_args["channel"],
    +            ts=self._stream_ts,
    +            blocks=blocks,
    +            markdown_text=self._buffer,
    +            metadata=metadata,
    +            **kwargs,
    +        )
    +        self._state = "completed"
    +        return response
    +
    +    async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse:
    +        """Flush the internal buffer by making appropriate API calls."""
    +        if not self._stream_ts:
    +            response = await self._client.chat_startStream(
    +                **self._stream_args,
    +                token=self._token,
    +                **kwargs,
    +                markdown_text=self._buffer,
    +            )
    +            self._stream_ts = response.get("ts")
    +            self._state = "in_progress"
    +        else:
    +            response = await self._client.chat_appendStream(
    +                token=self._token,
    +                channel=self._stream_args["channel"],
    +                ts=self._stream_ts,
    +                **kwargs,
    +                markdown_text=self._buffer,
    +            )
    +        self._buffer = ""
    +        return response
    +
    +

    A helper class for streaming markdown text into a conversation using the chat streaming APIs.

    +

    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API +methods, with automatic buffering and state management.

    +

    Initialize a new ChatStream instance.

    +

    The init method creates a unique ChatStream instance that keeps track of one chat stream.

    +

    Args

    +
    +
    client
    +
    The WebClient instance to use for API calls.
    +
    channel
    +
    An encoded ID that represents a channel, private group, or DM.
    +
    logger
    +
    A logging channel for outputs.
    +
    thread_ts
    +
    Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
    +
    recipient_team_id
    +
    The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
    +
    recipient_user_id
    +
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    buffer_size
    +
    The length of markdown_text to buffer in-memory before calling a method. Increasing this value +decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Methods

    +
    +
    +async def append(self, *, markdown_text: str, **kwargs) ‑> AsyncSlackResponse | None +
    +
    +
    + +Expand source code + +
    async def append(
    +    self,
    +    *,
    +    markdown_text: str,
    +    **kwargs,
    +) -> Optional[AsyncSlackResponse]:
    +    """Append to the stream.
    +
    +    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
    +    is stopped this method cannot be called.
    +
    +    Args:
    +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +          what will be appended to the message received so far.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        AsyncSlackResponse if the buffer was flushed, None if buffering.
    +
    +    Raises:
    +        SlackRequestError: If the stream is already completed.
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    if self._state == "completed":
    +        raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
    +    if kwargs.get("token"):
    +        self._token = kwargs.pop("token")
    +    self._buffer += markdown_text
    +    if len(self._buffer) >= self._buffer_size:
    +        return await self._flush_buffer(**kwargs)
    +    details = {
    +        "buffer_length": len(self._buffer),
    +        "buffer_size": self._buffer_size,
    +        "channel": self._stream_args.get("channel"),
    +        "recipient_team_id": self._stream_args.get("recipient_team_id"),
    +        "recipient_user_id": self._stream_args.get("recipient_user_id"),
    +        "thread_ts": self._stream_args.get("thread_ts"),
    +    }
    +    self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
    +    return None
    +
    +

    Append to the stream.

    +

    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream +is stopped this method cannot be called.

    +

    Args

    +
    +
    markdown_text
    +
    Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    AsyncSlackResponse if the buffer was flushed, None if buffering.

    +

    Raises

    +
    +
    SlackRequestError
    +
    If the stream is already completed.
    +
    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    +
    +async def stop(self,
    *,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def stop(
    +    self,
    +    *,
    +    markdown_text: Optional[str] = None,
    +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
    +    metadata: Optional[Union[Dict, Metadata]] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Stop the stream and finalize the message.
    +
    +    Args:
    +        blocks: A list of blocks that will be rendered at the bottom of the finalized message.
    +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +          what will be appended to the message received so far.
    +        metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
    +          post to Slack is accessible to any app or user who is a member of that workspace.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        AsyncSlackResponse from the chat.stopStream API call.
    +
    +    Raises:
    +        SlackRequestError: If the stream is already completed.
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    if self._state == "completed":
    +        raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
    +    if kwargs.get("token"):
    +        self._token = kwargs.pop("token")
    +    if markdown_text:
    +        self._buffer += markdown_text
    +    if not self._stream_ts:
    +        response = await self._client.chat_startStream(
    +            **self._stream_args,
    +            token=self._token,
    +        )
    +        if not response.get("ts"):
    +            raise e.SlackRequestError("Failed to stop stream: stream not started")
    +        self._stream_ts = str(response["ts"])
    +        self._state = "in_progress"
    +    response = await self._client.chat_stopStream(
    +        token=self._token,
    +        channel=self._stream_args["channel"],
    +        ts=self._stream_ts,
    +        blocks=blocks,
    +        markdown_text=self._buffer,
    +        metadata=metadata,
    +        **kwargs,
    +    )
    +    self._state = "completed"
    +    return response
    +
    +

    Stop the stream and finalize the message.

    +

    Args

    +
    +
    blocks
    +
    A list of blocks that will be rendered at the bottom of the finalized message.
    +
    markdown_text
    +
    Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
    +
    metadata
    +
    JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you +post to Slack is accessible to any app or user who is a member of that workspace.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    AsyncSlackResponse from the chat.stopStream API call.

    +

    Raises

    +
    +
    SlackRequestError
    +
    If the stream is already completed.
    +
    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/reference/web/async_client.html b/docs/reference/web/async_client.html index 9d895fea8..7a46cf18c 100644 --- a/docs/reference/web/async_client.html +++ b/docs/reference/web/async_client.html @@ -2952,6 +2952,69 @@

    Classes

    kwargs = _remove_none_values(kwargs) return await self.api_call("chat.stopStream", json=kwargs) + async def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = await client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="**hello wo") + await streamer.append(markdown_text="rld!**") + await streamer.stop() + ``` + """ + return AsyncChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + async def chat_unfurl( self, *, @@ -10242,6 +10305,122 @@

    Methods

    Stops a streaming conversation. https://api.slack.com/methods/chat.stopStream

    +
    +async def chat_stream(self,
    *,
    buffer_size: int = 256,
    channel: str,
    thread_ts: str,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> AsyncChatStream
    +
    +
    +
    + +Expand source code + +
    async def chat_stream(
    +    self,
    +    *,
    +    buffer_size: int = 256,
    +    channel: str,
    +    thread_ts: str,
    +    recipient_team_id: Optional[str] = None,
    +    recipient_user_id: Optional[str] = None,
    +    **kwargs,
    +) -> AsyncChatStream:
    +    """Stream markdown text into a conversation.
    +
    +    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message,
    +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
    +
    +    The following methods are used:
    +
    +    - chat.startStream: Starts a new streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
    +    - chat.appendStream: Appends text to an existing streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
    +    - chat.stopStream: Stops a streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
    +
    +    Args:
    +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
    +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
    +          limits. Default: 256.
    +        channel: An encoded ID that represents a channel, private group, or DM.
    +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
    +          request.
    +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
    +          streaming to channels.
    +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        ChatStream instance for managing the stream
    +
    +    Example:
    +        ```python
    +        streamer = await client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        await streamer.append(markdown_text="**hello wo")
    +        await streamer.append(markdown_text="rld!**")
    +        await streamer.stop()
    +        ```
    +    """
    +    return AsyncChatStream(
    +        self,
    +        logger=self._logger,
    +        channel=channel,
    +        thread_ts=thread_ts,
    +        recipient_team_id=recipient_team_id,
    +        recipient_user_id=recipient_user_id,
    +        buffer_size=buffer_size,
    +        **kwargs,
    +    )
    +
    +

    Stream markdown text into a conversation.

    +

    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

    +

    The following methods are used:

    +
      +
    • chat.startStream: Starts a new streaming conversation. +Reference.
    • +
    • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
    • +
    • chat.stopStream: Stops a streaming conversation. +Reference.
    • +
    +

    Args

    +
    +
    buffer_size
    +
    The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
    +
    channel
    +
    An encoded ID that represents a channel, private group, or DM.
    +
    thread_ts
    +
    Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
    +
    recipient_team_id
    +
    The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
    +
    recipient_user_id
    +
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    ChatStream instance for managing the stream

    +

    Example

    +
    streamer = await client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +await streamer.append(markdown_text="**hello wo")
    +await streamer.append(markdown_text="rld!**")
    +await streamer.stop()
    +
    +
    async def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    @@ -14759,6 +14938,7 @@

    chat_scheduledMessages_list
  • chat_startStream
  • chat_stopStream
  • +
  • chat_stream
  • chat_unfurl
  • chat_update
  • conversations_acceptSharedInvite
  • diff --git a/docs/reference/web/chat_stream.html b/docs/reference/web/chat_stream.html new file mode 100644 index 000000000..94d96e5eb --- /dev/null +++ b/docs/reference/web/chat_stream.html @@ -0,0 +1,506 @@ + + + + + + +slack_sdk.web.chat_stream API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_sdk.web.chat_stream

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class ChatStream +(client: WebClient,
    *,
    channel: str,
    logger: logging.Logger,
    thread_ts: str,
    buffer_size: int,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs)
    +
    +
    +
    + +Expand source code + +
    class ChatStream:
    +    """A helper class for streaming markdown text into a conversation using the chat streaming APIs.
    +
    +    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API
    +    methods, with automatic buffering and state management.
    +    """
    +
    +    def __init__(
    +        self,
    +        client: "WebClient",
    +        *,
    +        channel: str,
    +        logger: logging.Logger,
    +        thread_ts: str,
    +        buffer_size: int,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        **kwargs,
    +    ):
    +        """Initialize a new ChatStream instance.
    +
    +        The __init__ method creates a unique ChatStream instance that keeps track of one chat stream.
    +
    +        Args:
    +            client: The WebClient instance to use for API calls.
    +            channel: An encoded ID that represents a channel, private group, or DM.
    +            logger: A logging channel for outputs.
    +            thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
    +              request.
    +            recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
    +              streaming to channels.
    +            recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +            buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value
    +              decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +        """
    +        self._client = client
    +        self._logger = logger
    +        self._token: Optional[str] = kwargs.pop("token", None)
    +        self._stream_args = {
    +            "channel": channel,
    +            "thread_ts": thread_ts,
    +            "recipient_team_id": recipient_team_id,
    +            "recipient_user_id": recipient_user_id,
    +            **kwargs,
    +        }
    +        self._buffer = ""
    +        self._state = "starting"
    +        self._stream_ts: Optional[str] = None
    +        self._buffer_size = buffer_size
    +
    +    def append(
    +        self,
    +        *,
    +        markdown_text: str,
    +        **kwargs,
    +    ) -> Optional[SlackResponse]:
    +        """Append to the stream.
    +
    +        The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
    +        is stopped this method cannot be called.
    +
    +        Args:
    +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +              what will be appended to the message received so far.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +
    +        Returns:
    +            SlackResponse if the buffer was flushed, None if buffering.
    +
    +        Raises:
    +            SlackRequestError: If the stream is already completed.
    +
    +        Example:
    +            ```python
    +            streamer = client.chat_stream(
    +                channel="C0123456789",
    +                thread_ts="1700000001.123456",
    +                recipient_team_id="T0123456789",
    +                recipient_user_id="U0123456789",
    +            )
    +            streamer.append(markdown_text="**hello wo")
    +            streamer.append(markdown_text="rld!**")
    +            streamer.stop()
    +            ```
    +        """
    +        if self._state == "completed":
    +            raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
    +        if kwargs.get("token"):
    +            self._token = kwargs.pop("token")
    +        self._buffer += markdown_text
    +        if len(self._buffer) >= self._buffer_size:
    +            return self._flush_buffer(**kwargs)
    +        details = {
    +            "buffer_length": len(self._buffer),
    +            "buffer_size": self._buffer_size,
    +            "channel": self._stream_args.get("channel"),
    +            "recipient_team_id": self._stream_args.get("recipient_team_id"),
    +            "recipient_user_id": self._stream_args.get("recipient_user_id"),
    +            "thread_ts": self._stream_args.get("thread_ts"),
    +        }
    +        self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
    +        return None
    +
    +    def stop(
    +        self,
    +        *,
    +        markdown_text: Optional[str] = None,
    +        blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
    +        metadata: Optional[Union[Dict, Metadata]] = None,
    +        **kwargs,
    +    ) -> SlackResponse:
    +        """Stop the stream and finalize the message.
    +
    +        Args:
    +            blocks: A list of blocks that will be rendered at the bottom of the finalized message.
    +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +              what will be appended to the message received so far.
    +            metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
    +              post to Slack is accessible to any app or user who is a member of that workspace.
    +            **kwargs: Additional arguments passed to the underlying API calls.
    +
    +        Returns:
    +            SlackResponse from the chat.stopStream API call.
    +
    +        Raises:
    +            SlackRequestError: If the stream is already completed.
    +
    +        Example:
    +            ```python
    +            streamer = client.chat_stream(
    +                channel="C0123456789",
    +                thread_ts="1700000001.123456",
    +                recipient_team_id="T0123456789",
    +                recipient_user_id="U0123456789",
    +            )
    +            streamer.append(markdown_text="**hello wo")
    +            streamer.append(markdown_text="rld!**")
    +            streamer.stop()
    +            ```
    +        """
    +        if self._state == "completed":
    +            raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
    +        if kwargs.get("token"):
    +            self._token = kwargs.pop("token")
    +        if markdown_text:
    +            self._buffer += markdown_text
    +        if not self._stream_ts:
    +            response = self._client.chat_startStream(
    +                **self._stream_args,
    +                token=self._token,
    +            )
    +            if not response.get("ts"):
    +                raise e.SlackRequestError("Failed to stop stream: stream not started")
    +            self._stream_ts = str(response["ts"])
    +            self._state = "in_progress"
    +        response = self._client.chat_stopStream(
    +            token=self._token,
    +            channel=self._stream_args["channel"],
    +            ts=self._stream_ts,
    +            blocks=blocks,
    +            markdown_text=self._buffer,
    +            metadata=metadata,
    +            **kwargs,
    +        )
    +        self._state = "completed"
    +        return response
    +
    +    def _flush_buffer(self, **kwargs) -> SlackResponse:
    +        """Flush the internal buffer by making appropriate API calls."""
    +        if not self._stream_ts:
    +            response = self._client.chat_startStream(
    +                **self._stream_args,
    +                token=self._token,
    +                **kwargs,
    +                markdown_text=self._buffer,
    +            )
    +            self._stream_ts = response.get("ts")
    +            self._state = "in_progress"
    +        else:
    +            response = self._client.chat_appendStream(
    +                token=self._token,
    +                channel=self._stream_args["channel"],
    +                ts=self._stream_ts,
    +                **kwargs,
    +                markdown_text=self._buffer,
    +            )
    +        self._buffer = ""
    +        return response
    +
    +

    A helper class for streaming markdown text into a conversation using the chat streaming APIs.

    +

    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API +methods, with automatic buffering and state management.

    +

    Initialize a new ChatStream instance.

    +

    The init method creates a unique ChatStream instance that keeps track of one chat stream.

    +

    Args

    +
    +
    client
    +
    The WebClient instance to use for API calls.
    +
    channel
    +
    An encoded ID that represents a channel, private group, or DM.
    +
    logger
    +
    A logging channel for outputs.
    +
    thread_ts
    +
    Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
    +
    recipient_team_id
    +
    The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
    +
    recipient_user_id
    +
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    buffer_size
    +
    The length of markdown_text to buffer in-memory before calling a method. Increasing this value +decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Methods

    +
    +
    +def append(self, *, markdown_text: str, **kwargs) ‑> SlackResponse | None +
    +
    +
    + +Expand source code + +
    def append(
    +    self,
    +    *,
    +    markdown_text: str,
    +    **kwargs,
    +) -> Optional[SlackResponse]:
    +    """Append to the stream.
    +
    +    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
    +    is stopped this method cannot be called.
    +
    +    Args:
    +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +          what will be appended to the message received so far.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        SlackResponse if the buffer was flushed, None if buffering.
    +
    +    Raises:
    +        SlackRequestError: If the stream is already completed.
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    if self._state == "completed":
    +        raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
    +    if kwargs.get("token"):
    +        self._token = kwargs.pop("token")
    +    self._buffer += markdown_text
    +    if len(self._buffer) >= self._buffer_size:
    +        return self._flush_buffer(**kwargs)
    +    details = {
    +        "buffer_length": len(self._buffer),
    +        "buffer_size": self._buffer_size,
    +        "channel": self._stream_args.get("channel"),
    +        "recipient_team_id": self._stream_args.get("recipient_team_id"),
    +        "recipient_user_id": self._stream_args.get("recipient_user_id"),
    +        "thread_ts": self._stream_args.get("thread_ts"),
    +    }
    +    self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
    +    return None
    +
    +

    Append to the stream.

    +

    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream +is stopped this method cannot be called.

    +

    Args

    +
    +
    markdown_text
    +
    Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    SlackResponse if the buffer was flushed, None if buffering.

    +

    Raises

    +
    +
    SlackRequestError
    +
    If the stream is already completed.
    +
    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    +
    +def stop(self,
    *,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def stop(
    +    self,
    +    *,
    +    markdown_text: Optional[str] = None,
    +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
    +    metadata: Optional[Union[Dict, Metadata]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Stop the stream and finalize the message.
    +
    +    Args:
    +        blocks: A list of blocks that will be rendered at the bottom of the finalized message.
    +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
    +          what will be appended to the message received so far.
    +        metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
    +          post to Slack is accessible to any app or user who is a member of that workspace.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        SlackResponse from the chat.stopStream API call.
    +
    +    Raises:
    +        SlackRequestError: If the stream is already completed.
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    if self._state == "completed":
    +        raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
    +    if kwargs.get("token"):
    +        self._token = kwargs.pop("token")
    +    if markdown_text:
    +        self._buffer += markdown_text
    +    if not self._stream_ts:
    +        response = self._client.chat_startStream(
    +            **self._stream_args,
    +            token=self._token,
    +        )
    +        if not response.get("ts"):
    +            raise e.SlackRequestError("Failed to stop stream: stream not started")
    +        self._stream_ts = str(response["ts"])
    +        self._state = "in_progress"
    +    response = self._client.chat_stopStream(
    +        token=self._token,
    +        channel=self._stream_args["channel"],
    +        ts=self._stream_ts,
    +        blocks=blocks,
    +        markdown_text=self._buffer,
    +        metadata=metadata,
    +        **kwargs,
    +    )
    +    self._state = "completed"
    +    return response
    +
    +

    Stop the stream and finalize the message.

    +

    Args

    +
    +
    blocks
    +
    A list of blocks that will be rendered at the bottom of the finalized message.
    +
    markdown_text
    +
    Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
    +
    metadata
    +
    JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you +post to Slack is accessible to any app or user who is a member of that workspace.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    SlackResponse from the chat.stopStream API call.

    +

    Raises

    +
    +
    SlackRequestError
    +
    If the stream is already completed.
    +
    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/reference/web/client.html b/docs/reference/web/client.html index e6704424c..162df0964 100644 --- a/docs/reference/web/client.html +++ b/docs/reference/web/client.html @@ -2952,6 +2952,69 @@

    Classes

    kwargs = _remove_none_values(kwargs) return self.api_call("chat.stopStream", json=kwargs) + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -10242,6 +10305,122 @@

    Methods

    Stops a streaming conversation. https://api.slack.com/methods/chat.stopStream

    +
    +def chat_stream(self,
    *,
    buffer_size: int = 256,
    channel: str,
    thread_ts: str,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> ChatStream
    +
    +
    +
    + +Expand source code + +
    def chat_stream(
    +    self,
    +    *,
    +    buffer_size: int = 256,
    +    channel: str,
    +    thread_ts: str,
    +    recipient_team_id: Optional[str] = None,
    +    recipient_user_id: Optional[str] = None,
    +    **kwargs,
    +) -> ChatStream:
    +    """Stream markdown text into a conversation.
    +
    +    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message,
    +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
    +
    +    The following methods are used:
    +
    +    - chat.startStream: Starts a new streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
    +    - chat.appendStream: Appends text to an existing streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
    +    - chat.stopStream: Stops a streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
    +
    +    Args:
    +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
    +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
    +          limits. Default: 256.
    +        channel: An encoded ID that represents a channel, private group, or DM.
    +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
    +          request.
    +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
    +          streaming to channels.
    +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        ChatStream instance for managing the stream
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    return ChatStream(
    +        self,
    +        logger=self._logger,
    +        channel=channel,
    +        thread_ts=thread_ts,
    +        recipient_team_id=recipient_team_id,
    +        recipient_user_id=recipient_user_id,
    +        buffer_size=buffer_size,
    +        **kwargs,
    +    )
    +
    +

    Stream markdown text into a conversation.

    +

    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

    +

    The following methods are used:

    +
      +
    • chat.startStream: Starts a new streaming conversation. +Reference.
    • +
    • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
    • +
    • chat.stopStream: Stops a streaming conversation. +Reference.
    • +
    +

    Args

    +
    +
    buffer_size
    +
    The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
    +
    channel
    +
    An encoded ID that represents a channel, private group, or DM.
    +
    thread_ts
    +
    Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
    +
    recipient_team_id
    +
    The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
    +
    recipient_user_id
    +
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    ChatStream instance for managing the stream

    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -14758,6 +14937,7 @@

    chat_scheduledMessages_list
  • chat_startStream
  • chat_stopStream
  • +
  • chat_stream
  • chat_unfurl
  • chat_update
  • conversations_acceptSharedInvite
  • diff --git a/docs/reference/web/index.html b/docs/reference/web/index.html index 2e9c01af9..c2df7f11f 100644 --- a/docs/reference/web/index.html +++ b/docs/reference/web/index.html @@ -47,6 +47,10 @@

    Sub-modules

    +
    slack_sdk.web.async_chat_stream
    +
    +
    +
    slack_sdk.web.async_client

    A Python module for interacting with Slack's Web API.

    @@ -63,6 +67,10 @@

    Sub-modules

    A Python module for interacting with Slack's Web API.

    +
    slack_sdk.web.chat_stream
    +
    +
    +
    slack_sdk.web.client

    A Python module for interacting with Slack's Web API.

    @@ -3313,6 +3321,69 @@

    Raises

    kwargs = _remove_none_values(kwargs) return self.api_call("chat.stopStream", json=kwargs) + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -10603,6 +10674,122 @@

    Methods

    Stops a streaming conversation. https://api.slack.com/methods/chat.stopStream

    +
    +def chat_stream(self,
    *,
    buffer_size: int = 256,
    channel: str,
    thread_ts: str,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> ChatStream
    +
    +
    +
    + +Expand source code + +
    def chat_stream(
    +    self,
    +    *,
    +    buffer_size: int = 256,
    +    channel: str,
    +    thread_ts: str,
    +    recipient_team_id: Optional[str] = None,
    +    recipient_user_id: Optional[str] = None,
    +    **kwargs,
    +) -> ChatStream:
    +    """Stream markdown text into a conversation.
    +
    +    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message,
    +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
    +
    +    The following methods are used:
    +
    +    - chat.startStream: Starts a new streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
    +    - chat.appendStream: Appends text to an existing streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
    +    - chat.stopStream: Stops a streaming conversation.
    +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
    +
    +    Args:
    +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
    +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
    +          limits. Default: 256.
    +        channel: An encoded ID that represents a channel, private group, or DM.
    +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
    +          request.
    +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
    +          streaming to channels.
    +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +        **kwargs: Additional arguments passed to the underlying API calls.
    +
    +    Returns:
    +        ChatStream instance for managing the stream
    +
    +    Example:
    +        ```python
    +        streamer = client.chat_stream(
    +            channel="C0123456789",
    +            thread_ts="1700000001.123456",
    +            recipient_team_id="T0123456789",
    +            recipient_user_id="U0123456789",
    +        )
    +        streamer.append(markdown_text="**hello wo")
    +        streamer.append(markdown_text="rld!**")
    +        streamer.stop()
    +        ```
    +    """
    +    return ChatStream(
    +        self,
    +        logger=self._logger,
    +        channel=channel,
    +        thread_ts=thread_ts,
    +        recipient_team_id=recipient_team_id,
    +        recipient_user_id=recipient_user_id,
    +        buffer_size=buffer_size,
    +        **kwargs,
    +    )
    +
    +

    Stream markdown text into a conversation.

    +

    This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

    +

    The following methods are used:

    +
      +
    • chat.startStream: Starts a new streaming conversation. +Reference.
    • +
    • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
    • +
    • chat.stopStream: Stops a streaming conversation. +Reference.
    • +
    +

    Args

    +
    +
    buffer_size
    +
    The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
    +
    channel
    +
    An encoded ID that represents a channel, private group, or DM.
    +
    thread_ts
    +
    Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
    +
    recipient_team_id
    +
    The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
    +
    recipient_user_id
    +
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    **kwargs
    +
    Additional arguments passed to the underlying API calls.
    +
    +

    Returns

    +

    ChatStream instance for managing the stream

    +

    Example

    +
    streamer = client.chat_stream(
    +    channel="C0123456789",
    +    thread_ts="1700000001.123456",
    +    recipient_team_id="T0123456789",
    +    recipient_user_id="U0123456789",
    +)
    +streamer.append(markdown_text="**hello wo")
    +streamer.append(markdown_text="rld!**")
    +streamer.stop()
    +
    +
    def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -14961,10 +15148,12 @@

    Inherited members

  • Sub-modules

    • slack_sdk.web.async_base_client
    • +
    • slack_sdk.web.async_chat_stream
    • slack_sdk.web.async_client
    • slack_sdk.web.async_internal_utils
    • slack_sdk.web.async_slack_response
    • slack_sdk.web.base_client
    • +
    • slack_sdk.web.chat_stream
    • slack_sdk.web.client
    • slack_sdk.web.deprecation
    • slack_sdk.web.file_upload_v2_result
    • @@ -15143,6 +15332,7 @@

      Web
    • chat_scheduledMessages_list
    • chat_startStream
    • chat_stopStream
    • +
    • chat_stream
    • chat_unfurl
    • chat_update
    • conversations_acceptSharedInvite
    • diff --git a/scripts/codegen.py b/scripts/codegen.py index f58de8bd1..3633faf35 100644 --- a/scripts/codegen.py +++ b/scripts/codegen.py @@ -1,5 +1,5 @@ -import sys import argparse +import sys parser = argparse.ArgumentParser() parser.add_argument("-p", "--path", help="Path to the project source code.", type=str) @@ -35,7 +35,6 @@ "from .async_base_client import AsyncBaseClient, AsyncSlackResponse", async_source, ) - # from slack_sdk import WebClient async_source = re.sub( r"class WebClient\(BaseClient\):", "class AsyncWebClient(AsyncBaseClient):", @@ -47,6 +46,28 @@ async_source, ) async_source = re.sub(r"= WebClient\(", "= AsyncWebClient(", async_source) + async_source = re.sub( + "from slack_sdk.web.chat_stream import ChatStream", + "from slack_sdk.web.async_chat_stream import AsyncChatStream", + async_source, + ) + async_source = re.sub(r"ChatStream:", "AsyncChatStream:", async_source) + async_source = re.sub(r"ChatStream\(", "AsyncChatStream(", async_source) + async_source = re.sub( + r" client.chat_stream\(", + " await client.chat_stream(", + async_source, + ) + async_source = re.sub( + r" streamer.append\(", + " await streamer.append(", + async_source, + ) + async_source = re.sub( + r" streamer.stop\(", + " await streamer.stop(", + async_source, + ) async_source = re.sub( r" self.files_getUploadURLExternal\(", " await self.files_getUploadURLExternal(", @@ -98,5 +119,35 @@ legacy_source, ) legacy_source = re.sub(r"= WebClient\(", "= LegacyWebClient(", legacy_source) + legacy_source = re.sub(r"^from slack_sdk.web.chat_stream import ChatStream\n", "", legacy_source, flags=re.MULTILINE) + legacy_source = re.sub(r"(?s)def chat_stream.*?(?=def)", "", legacy_source) with open(f"{args.path}/slack_sdk/web/legacy_client.py", "w") as output: output.write(legacy_source) + +with open(f"{args.path}/slack_sdk/web/chat_stream.py", "r") as original: + source = original.read() + import re + + async_source = header + source + async_source = re.sub( + "from slack_sdk.web.slack_response import SlackResponse", + "from slack_sdk.web.async_slack_response import AsyncSlackResponse", + async_source, + ) + async_source = re.sub( + r"from slack_sdk import WebClient", + "from slack_sdk.web.async_client import AsyncWebClient", + async_source, + ) + async_source = re.sub("class ChatStream", "class AsyncChatStream", async_source) + async_source = re.sub('"WebClient"', '"AsyncWebClient"', async_source) + async_source = re.sub(r"Optional\[SlackResponse\]", "Optional[AsyncSlackResponse]", async_source) + async_source = re.sub(r"SlackResponse ", "AsyncSlackResponse ", async_source) + async_source = re.sub(r"SlackResponse:", "AsyncSlackResponse:", async_source) + async_source = re.sub(r"def append\(", "async def append(", async_source) + async_source = re.sub(r"def stop\(", "async def stop(", async_source) + async_source = re.sub(r"def _flush_buffer\(", "async def _flush_buffer(", async_source) + async_source = re.sub("self._client.chat_", "await self._client.chat_", async_source) + async_source = re.sub("self._flush_buffer", "await self._flush_buffer", async_source) + with open(f"{args.path}/slack_sdk/web/async_chat_stream.py", "w") as output: + output.write(async_source) diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py new file mode 100644 index 000000000..4661f19dd --- /dev/null +++ b/slack_sdk/web/async_chat_stream.py @@ -0,0 +1,212 @@ +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# *** DO NOT EDIT THIS FILE *** +# +# 1) Modify slack_sdk/web/client.py +# 2) Run `python scripts/codegen.py` +# 3) Run `black slack_sdk/` +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import json +import logging +from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union + +import slack_sdk.errors as e +from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.metadata import Metadata +from slack_sdk.web.async_slack_response import AsyncSlackResponse + +if TYPE_CHECKING: + from slack_sdk.web.async_client import AsyncWebClient + + +class AsyncChatStream: + """A helper class for streaming markdown text into a conversation using the chat streaming APIs. + + This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API + methods, with automatic buffering and state management. + """ + + def __init__( + self, + client: "AsyncWebClient", + *, + channel: str, + logger: logging.Logger, + thread_ts: str, + buffer_size: int, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ): + """Initialize a new ChatStream instance. + + The __init__ method creates a unique ChatStream instance that keeps track of one chat stream. + + Args: + client: The WebClient instance to use for API calls. + channel: An encoded ID that represents a channel, private group, or DM. + logger: A logging channel for outputs. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value + decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. + **kwargs: Additional arguments passed to the underlying API calls. + """ + self._client = client + self._logger = logger + self._token: Optional[str] = kwargs.pop("token", None) + self._stream_args = { + "channel": channel, + "thread_ts": thread_ts, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + **kwargs, + } + self._buffer = "" + self._state = "starting" + self._stream_ts: Optional[str] = None + self._buffer_size = buffer_size + + async def append( + self, + *, + markdown_text: str, + **kwargs, + ) -> Optional[AsyncSlackResponse]: + """Append to the stream. + + The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream + is stopped this method cannot be called. + + Args: + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + AsyncSlackResponse if the buffer was flushed, None if buffering. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size: + return await self._flush_buffer(**kwargs) + details = { + "buffer_length": len(self._buffer), + "buffer_size": self._buffer_size, + "channel": self._stream_args.get("channel"), + "recipient_team_id": self._stream_args.get("recipient_team_id"), + "recipient_user_id": self._stream_args.get("recipient_user_id"), + "thread_ts": self._stream_args.get("thread_ts"), + } + self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}") + return None + + async def stop( + self, + *, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Stop the stream and finalize the message. + + Args: + blocks: A list of blocks that will be rendered at the bottom of the finalized message. + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you + post to Slack is accessible to any app or user who is a member of that workspace. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + AsyncSlackResponse from the chat.stopStream API call. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + if markdown_text: + self._buffer += markdown_text + if not self._stream_ts: + response = await self._client.chat_startStream( + **self._stream_args, + token=self._token, + ) + if not response.get("ts"): + raise e.SlackRequestError("Failed to stop stream: stream not started") + self._stream_ts = str(response["ts"]) + self._state = "in_progress" + response = await self._client.chat_stopStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + blocks=blocks, + markdown_text=self._buffer, + metadata=metadata, + **kwargs, + ) + self._state = "completed" + return response + + async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer by making appropriate API calls.""" + if not self._stream_ts: + response = await self._client.chat_startStream( + **self._stream_args, + token=self._token, + **kwargs, + markdown_text=self._buffer, + ) + self._stream_ts = response.get("ts") + self._state = "in_progress" + else: + response = await self._client.chat_appendStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + **kwargs, + markdown_text=self._buffer, + ) + self._buffer = "" + return response diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index 3f30bad5a..f4aa8a17f 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -18,6 +18,7 @@ import slack_sdk.errors as e from slack_sdk.models.views import View +from slack_sdk.web.async_chat_stream import AsyncChatStream from ..models.attachments import Attachment from ..models.blocks import Block @@ -2930,6 +2931,69 @@ async def chat_stopStream( kwargs = _remove_none_values(kwargs) return await self.api_call("chat.stopStream", json=kwargs) + async def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = await client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="**hello wo") + await streamer.append(markdown_text="rld!**") + await streamer.stop() + ``` + """ + return AsyncChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + async def chat_unfurl( self, *, diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py new file mode 100644 index 000000000..1a379c9cb --- /dev/null +++ b/slack_sdk/web/chat_stream.py @@ -0,0 +1,202 @@ +import json +import logging +from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union + +import slack_sdk.errors as e +from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.metadata import Metadata +from slack_sdk.web.slack_response import SlackResponse + +if TYPE_CHECKING: + from slack_sdk import WebClient + + +class ChatStream: + """A helper class for streaming markdown text into a conversation using the chat streaming APIs. + + This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API + methods, with automatic buffering and state management. + """ + + def __init__( + self, + client: "WebClient", + *, + channel: str, + logger: logging.Logger, + thread_ts: str, + buffer_size: int, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ): + """Initialize a new ChatStream instance. + + The __init__ method creates a unique ChatStream instance that keeps track of one chat stream. + + Args: + client: The WebClient instance to use for API calls. + channel: An encoded ID that represents a channel, private group, or DM. + logger: A logging channel for outputs. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value + decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. + **kwargs: Additional arguments passed to the underlying API calls. + """ + self._client = client + self._logger = logger + self._token: Optional[str] = kwargs.pop("token", None) + self._stream_args = { + "channel": channel, + "thread_ts": thread_ts, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + **kwargs, + } + self._buffer = "" + self._state = "starting" + self._stream_ts: Optional[str] = None + self._buffer_size = buffer_size + + def append( + self, + *, + markdown_text: str, + **kwargs, + ) -> Optional[SlackResponse]: + """Append to the stream. + + The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream + is stopped this method cannot be called. + + Args: + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + SlackResponse if the buffer was flushed, None if buffering. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size: + return self._flush_buffer(**kwargs) + details = { + "buffer_length": len(self._buffer), + "buffer_size": self._buffer_size, + "channel": self._stream_args.get("channel"), + "recipient_team_id": self._stream_args.get("recipient_team_id"), + "recipient_user_id": self._stream_args.get("recipient_user_id"), + "thread_ts": self._stream_args.get("thread_ts"), + } + self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}") + return None + + def stop( + self, + *, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stop the stream and finalize the message. + + Args: + blocks: A list of blocks that will be rendered at the bottom of the finalized message. + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you + post to Slack is accessible to any app or user who is a member of that workspace. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + SlackResponse from the chat.stopStream API call. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + if markdown_text: + self._buffer += markdown_text + if not self._stream_ts: + response = self._client.chat_startStream( + **self._stream_args, + token=self._token, + ) + if not response.get("ts"): + raise e.SlackRequestError("Failed to stop stream: stream not started") + self._stream_ts = str(response["ts"]) + self._state = "in_progress" + response = self._client.chat_stopStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + blocks=blocks, + markdown_text=self._buffer, + metadata=metadata, + **kwargs, + ) + self._state = "completed" + return response + + def _flush_buffer(self, **kwargs) -> SlackResponse: + """Flush the internal buffer by making appropriate API calls.""" + if not self._stream_ts: + response = self._client.chat_startStream( + **self._stream_args, + token=self._token, + **kwargs, + markdown_text=self._buffer, + ) + self._stream_ts = response.get("ts") + self._state = "in_progress" + else: + response = self._client.chat_appendStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + **kwargs, + markdown_text=self._buffer, + ) + self._buffer = "" + return response diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 8c15f983f..f95a2a726 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -8,6 +8,7 @@ import slack_sdk.errors as e from slack_sdk.models.views import View +from slack_sdk.web.chat_stream import ChatStream from ..models.attachments import Attachment from ..models.blocks import Block @@ -2920,6 +2921,69 @@ def chat_stopStream( kwargs = _remove_none_values(kwargs) return self.api_call("chat.stopStream", json=kwargs) + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a coversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py new file mode 100644 index 000000000..75c13c8c2 --- /dev/null +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -0,0 +1,188 @@ +import json +import unittest +from urllib.parse import parse_qs, urlparse + +from slack_sdk import WebClient +from slack_sdk.errors import SlackRequestError +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement +from slack_sdk.models.blocks.blocks import ContextActionsBlock +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.slack_sdk.web.mock_web_api_handler import MockHandler + + +class ChatStreamMockHandler(MockHandler): + """Extended mock handler that captures request bodies for chat stream methods""" + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + + # Standard auth and validation from parent + if self.is_valid_token() and self.is_valid_user_agent(): + token = self.headers["authorization"].split(" ")[1] + parsed_path = urlparse(self.path) + len_header = self.headers.get("Content-Length") or 0 + content_len = int(len_header) + post_body = self.rfile.read(content_len) + request_body = None + if post_body: + try: + post_body = post_body.decode("utf-8") + if post_body.startswith("{"): + request_body = json.loads(post_body) + else: + request_body = {k: v[0] for k, v in parse_qs(post_body).items()} + except UnicodeDecodeError: + pass + else: + if parsed_path and parsed_path.query: + request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} + + # Store request body for chat stream endpoints + if self.path in ["/chat.startStream", "/chat.appendStream", "/chat.stopStream"] and request_body: + if not hasattr(self.server, "chat_stream_requests"): + self.server.chat_stream_requests = {} + self.server.chat_stream_requests[self.path] = { + "token": token, + **request_body, + } + + # Load response file + pattern = str(token).split("xoxb-", 1)[1] + with open(f"tests/slack_sdk_fixture/web_response_{pattern}.json") as file: + body = json.load(file) + + else: + body = self.invalid_auth + + if not body: + body = self.not_found + + self.send_response(200) + self.set_common_headers() + self.wfile.write(json.dumps(body).encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + +class TestChatStream(unittest.TestCase): + def setUp(self): + setup_mock_web_api_server(self, ChatStreamMockHandler) + self.client = WebClient( + token="xoxb-chat_stream_test", + base_url="http://localhost:8888", + ) + + def tearDown(self): + cleanup_mock_web_api_server(self) + + def test_streams_a_short_message(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="nice!") + streamer.stop() + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 0) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual(stop_request.get("markdown_text"), "nice!") + + def test_streams_a_long_message(self): + streamer = self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + streamer.append(markdown_text="**this messag") + streamer.append(markdown_text="e is", token="xoxb-chat_stream_test_token1") + streamer.append(markdown_text=" bold!") + streamer.append(markdown_text="*") + streamer.stop( + blocks=[ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + positive_button=FeedbackButtonObject(text="good", value="+1"), + negative_button=FeedbackButtonObject(text="bad", value="-1"), + ), + IconButtonElement( + icon="trash", + text="delete", + ), + ], + ) + ], + markdown_text="*", + token="xoxb-chat_stream_test_token2", + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") + self.assertEqual(append_request.get("ts"), "123.123") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual( + json.dumps(stop_request.get("blocks")), + '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', + ) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") + self.assertEqual(stop_request.get("ts"), "123.123") + + def test_streams_errors_when_appending_to_an_unstarted_stream(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + token="xoxb-chat_stream_test_missing_ts", + ) + with self.assertRaisesRegex(SlackRequestError, r"^Failed to stop stream: stream not started$"): + streamer.stop() + + def test_streams_errors_when_appending_to_a_completed_stream(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + ) + streamer.append(markdown_text="nice!") + streamer.stop() + with self.assertRaisesRegex(SlackRequestError, r"^Cannot append to stream: stream state is completed$"): + streamer.append(markdown_text="more...") + with self.assertRaisesRegex(SlackRequestError, r"^Cannot stop stream: stream state is completed$"): + streamer.stop() diff --git a/tests/slack_sdk_async/web/test_async_chat_stream.py b/tests/slack_sdk_async/web/test_async_chat_stream.py new file mode 100644 index 000000000..212fee1e2 --- /dev/null +++ b/tests/slack_sdk_async/web/test_async_chat_stream.py @@ -0,0 +1,193 @@ +import json +import unittest +from urllib.parse import parse_qs, urlparse + +from slack_sdk.errors import SlackRequestError +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement +from slack_sdk.models.blocks.blocks import ContextActionsBlock +from slack_sdk.web.async_client import AsyncWebClient +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.slack_sdk_async.helpers import async_test + + +class ChatStreamMockHandler(MockHandler): + """Extended mock handler that captures request bodies for chat stream methods""" + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + + # Standard auth and validation from parent + if self.is_valid_token() and self.is_valid_user_agent(): + token = self.headers["authorization"].split(" ")[1] + parsed_path = urlparse(self.path) + len_header = self.headers.get("Content-Length") or 0 + content_len = int(len_header) + post_body = self.rfile.read(content_len) + request_body = None + if post_body: + try: + post_body = post_body.decode("utf-8") + if post_body.startswith("{"): + request_body = json.loads(post_body) + else: + request_body = {k: v[0] for k, v in parse_qs(post_body).items()} + except UnicodeDecodeError: + pass + else: + if parsed_path and parsed_path.query: + request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} + + # Store request body for chat stream endpoints + if self.path in ["/chat.startStream", "/chat.appendStream", "/chat.stopStream"] and request_body: + if not hasattr(self.server, "chat_stream_requests"): + self.server.chat_stream_requests = {} + self.server.chat_stream_requests[self.path] = { + "token": token, + **request_body, + } + + # Load response file + pattern = str(token).split("xoxb-", 1)[1] + with open(f"tests/slack_sdk_fixture/web_response_{pattern}.json") as file: + body = json.load(file) + + else: + body = self.invalid_auth + + if not body: + body = self.not_found + + self.send_response(200) + self.set_common_headers() + self.wfile.write(json.dumps(body).encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + +class TestAsyncChatStream(unittest.TestCase): + def setUp(self): + setup_mock_web_api_server(self, ChatStreamMockHandler) + self.client = AsyncWebClient( + token="xoxb-chat_stream_test", + base_url="http://localhost:8888", + ) + + def tearDown(self): + cleanup_mock_web_api_server(self) + + @async_test + async def test_streams_a_short_message(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="nice!") + await streamer.stop() + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 0) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual(stop_request.get("markdown_text"), "nice!") + + @async_test + async def test_streams_a_long_message(self): + streamer = await self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="**this messag") + await streamer.append(markdown_text="e is", token="xoxb-chat_stream_test_token1") + await streamer.append(markdown_text=" bold!") + await streamer.append(markdown_text="*") + await streamer.stop( + blocks=[ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + positive_button=FeedbackButtonObject(text="good", value="+1"), + negative_button=FeedbackButtonObject(text="bad", value="-1"), + ), + IconButtonElement( + icon="trash", + text="delete", + ), + ], + ) + ], + markdown_text="*", + token="xoxb-chat_stream_test_token2", + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") + self.assertEqual(append_request.get("ts"), "123.123") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual( + json.dumps(stop_request.get("blocks")), + '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', + ) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") + self.assertEqual(stop_request.get("ts"), "123.123") + + @async_test + async def test_streams_errors_when_appending_to_an_unstarted_stream(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + token="xoxb-chat_stream_test_missing_ts", + ) + with self.assertRaisesRegex(SlackRequestError, r"^Failed to stop stream: stream not started$"): + await streamer.stop() + + @async_test + async def test_streams_errors_when_appending_to_a_completed_stream(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="nice!") + await streamer.stop() + with self.assertRaisesRegex(SlackRequestError, r"^Cannot append to stream: stream state is completed$"): + await streamer.append(markdown_text="more...") + with self.assertRaisesRegex(SlackRequestError, r"^Cannot stop stream: stream state is completed$"): + await streamer.stop() diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test.json b/tests/slack_sdk_fixture/web_response_chat_stream_test.json new file mode 100644 index 000000000..2b5f29d01 --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test.json @@ -0,0 +1,4 @@ +{ + "ok": true, + "ts": "123.123" +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json @@ -0,0 +1,3 @@ +{ + "ok": true +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json @@ -0,0 +1,3 @@ +{ + "ok": true +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json @@ -0,0 +1,3 @@ +{ + "ok": true +}