diff --git a/docs/reference/index.html b/docs/reference/index.html index 7cc7723c1..31b766b61 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -2755,7 +2755,8 @@

Classes

*, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2766,8 +2767,10 @@

Classes

"channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -3009,6 +3012,8 @@

Classes

markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -3021,8 +3026,11 @@

Classes

"markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -3034,6 +3042,7 @@

Classes

markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -3046,6 +3055,7 @@

Classes

"markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -3060,6 +3070,7 @@

Classes

thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -3086,6 +3097,8 @@

Classes

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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3111,6 +3124,7 @@

Classes

thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10248,7 +10262,7 @@

Methods

Unarchives a channel.

-def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
*,
channel: str,
ts: str,
markdown_text: str | None = None,
chunks: Sequence[Dict | Chunk] | None = None,
**kwargs) ‑> SlackResponse
@@ -10260,7 +10274,8 @@

Methods

*, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10271,8 +10286,10 @@

Methods

"channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
@@ -10598,7 +10615,7 @@

Methods

https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

-def chat_startStream(self,
*,
channel: str,
thread_ts: str,
markdown_text: str | None = None,
recipient_team_id: str | None = None,
recipient_user_id: str | None = None,
**kwargs) ‑> SlackResponse
+def chat_startStream(self,
*,
channel: str,
thread_ts: str,
markdown_text: str | None = None,
recipient_team_id: str | None = None,
recipient_user_id: str | None = None,
chunks: Sequence[Dict | Chunk] | None = None,
task_display_mode: str | None = None,
**kwargs) ‑> SlackResponse
@@ -10613,6 +10630,8 @@

Methods

markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10625,8 +10644,11 @@

Methods

"markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
@@ -10634,7 +10656,7 @@

Methods

https://docs.slack.dev/reference/methods/chat.startStream

-def chat_stopStream(self,
*,
channel: str,
ts: str,
markdown_text: str | None = None,
blocks: str | Sequence[Dict | Block] | None = None,
metadata: Dict | Metadata | None = None,
**kwargs) ‑> SlackResponse
+def chat_stopStream(self,
*,
channel: str,
ts: str,
markdown_text: str | None = None,
blocks: str | Sequence[Dict | Block] | None = None,
metadata: Dict | Metadata | None = None,
chunks: Sequence[Dict | Chunk] | None = None,
**kwargs) ‑> SlackResponse
@@ -10649,6 +10671,7 @@

Methods

markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10661,6 +10684,7 @@

Methods

"markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10671,7 +10695,7 @@

Methods

https://docs.slack.dev/reference/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
+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,
task_display_mode: str | None = None,
**kwargs) ‑> ChatStream
@@ -10686,6 +10710,7 @@

Methods

thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10712,6 +10737,8 @@

Methods

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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10737,6 +10764,7 @@

Methods

thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10769,6 +10797,9 @@

Args

streaming to channels.
recipient_user_id
The encoded ID of the user to receive the streaming text. Required when streaming to channels.
+
task_display_mode
+
Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
**kwargs
Additional arguments passed to the underlying API calls.
diff --git a/docs/reference/models/basic_objects.html b/docs/reference/models/basic_objects.html index 193200f84..cda8d9c95 100644 --- a/docs/reference/models/basic_objects.html +++ b/docs/reference/models/basic_objects.html @@ -212,6 +212,7 @@

Subclasses

  • AbstractDialogSelector
  • DialogBuilder
  • DialogTextComponent
  • +
  • Chunk
  • Message
  • ContentItemEntityFields
  • EntityActionButton
  • diff --git a/docs/reference/models/blocks/basic_components.html b/docs/reference/models/blocks/basic_components.html index 2821b6a4b..50554265d 100644 --- a/docs/reference/models/blocks/basic_components.html +++ b/docs/reference/models/blocks/basic_components.html @@ -188,7 +188,7 @@

    Class variables

    Static methods

    -def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any]) +def parse(confirm: ConfirmObject | Dict[str, Any])
    @@ -324,7 +324,7 @@

    Class variables

    Static methods

    -def parse(config: ForwardRef('DispatchActionConfig') | Dict[str, Any]) +def parse(config: DispatchActionConfig | Dict[str, Any])
    @@ -446,7 +446,7 @@

    Class variables

    Static methods

    -def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +def parse(feedback_button: FeedbackButtonObject | Dict[str, Any])
    @@ -907,7 +907,7 @@

    Static methods

    Creates a simple Option instance with the same value and label

    -def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None
    @@ -1105,7 +1105,7 @@

    Class variables

    Static methods

    -def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None
    @@ -1536,7 +1536,7 @@

    Class variables

    Static methods

    -def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
    default_type: str = 'mrkdwn') ‑> TextObject | None
    +def parse(text: str | Dict[str, Any] | TextObject,
    default_type: str = 'mrkdwn') ‑> TextObject | None
    diff --git a/docs/reference/models/blocks/block_elements.html b/docs/reference/models/blocks/block_elements.html index 375ee07ce..e79ac10d1 100644 --- a/docs/reference/models/blocks/block_elements.html +++ b/docs/reference/models/blocks/block_elements.html @@ -130,6 +130,7 @@

    Subclasses

  • ImageElement
  • InteractiveElement
  • RichTextElement
  • +
  • UrlSourceElement
  • Class variables

    @@ -145,13 +146,13 @@

    Class variables

    Static methods

    -def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None +def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None
    -def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject]
    @@ -828,7 +829,7 @@

    Class variables

    Static methods

    -def parse(filter: dict | ForwardRef('ConversationFilter')) +def parse(filter: dict | ConversationFilter)
    @@ -3504,7 +3505,7 @@

    Class variables

    class RichTextInputElement -(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    +(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | RichTextBlock | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -4663,6 +4664,103 @@

    Inherited members

    +
    +class UrlSourceElement +(*, url: str, text: str, **others: Dict) +
    +
    +
    + +Expand source code + +
    class UrlSourceElement(BlockElement):
    +    type = "url"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "url",
    +                "text",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str,
    +        text: str,
    +        **others: Dict,
    +    ):
    +        """
    +        A URL source element that displays a URL source for referencing within a task card block.
    +        https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
    +
    +        Args:
    +            url (required): The URL type source.
    +            text (required): Display text for the URL.
    +        """
    +        super().__init__(type=self.type)
    +        show_unknown_key_warning(self, others)
    +        self.url = url
    +        self.text = text
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    A URL source element that displays a URL source for referencing within a task card block. +https://docs.slack.dev/reference/block-kit/block-elements/url-source-element

    +

    Args

    +
    +
    url : required
    +
    The URL type source.
    +
    text : required
    +
    Display text for the URL.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "url",
    +            "text",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class UserMultiSelectElement (*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_users: Sequence[str] | None = None,
    confirm: dict | ConfirmObject | None = None,
    max_selected_items: int | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -5319,6 +5417,13 @@

  • +

    UrlSourceElement

    + +
  • +
  • UserMultiSelectElement

    • attributes
    • diff --git a/docs/reference/models/blocks/blocks.html b/docs/reference/models/blocks/blocks.html index 722d12164..a4f511858 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -238,6 +238,10 @@

      Inherited members

      return RichTextBlock(**block) elif type == TableBlock.type: return TableBlock(**block) + elif type == TaskCardBlock.type: + return TaskCardBlock(**block) + elif type == PlanBlock.type: + return PlanBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -269,9 +273,11 @@

      Subclasses

    • ImageBlock
    • InputBlock
    • MarkdownBlock
    • +
    • PlanBlock
    • RichTextBlock
    • SectionBlock
    • TableBlock
    • +
    • TaskCardBlock
    • VideoBlock

    Class variables

    @@ -292,13 +298,13 @@

    Class variables

    Static methods

    -def parse(block: dict | ForwardRef('Block')) ‑> Block | None +def parse(block: dict | Block) ‑> Block | None
    -def parse_all(blocks: Sequence[dict | ForwardRef('Block')] | None) ‑> List[Block] +def parse_all(blocks: Sequence[dict | Block] | None) ‑> List[Block]
    @@ -1321,6 +1327,115 @@

    Inherited members

  • +
    +class PlanBlock +(*,
    title: str,
    tasks: Sequence[Dict | TaskCardBlock] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class PlanBlock(Block):
    +    type = "plan"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "title",
    +                "tasks",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        title: str,
    +        tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a collection of related tasks.
    +        https://docs.slack.dev/reference/block-kit/blocks/plan-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            title (required): Title of the plan in plain text
    +            tasks: Details of the task in the form of a single "rich_text" entity.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.title = title
    +        self.tasks = tasks
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a collection of related tasks. +https://docs.slack.dev/reference/block-kit/blocks/plan-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    title : required
    +
    Title of the plan in plain text
    +
    tasks
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "title",
    +            "tasks",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class RichTextBlock (*,
    elements: Sequence[dict | RichTextElement],
    block_id: str | None = None,
    **others: dict)
    @@ -1700,6 +1815,147 @@

    Inherited members

    +
    +class TaskCardBlock +(*,
    task_id: str,
    title: str,
    details: RichTextBlock | dict | None = None,
    output: RichTextBlock | dict | None = None,
    sources: Sequence[UrlSourceElement | dict] | None = None,
    status: str,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TaskCardBlock(Block):
    +    type = "task_card"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "task_id",
    +                "title",
    +                "details",
    +                "output",
    +                "sources",
    +                "status",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        task_id: str,
    +        title: str,
    +        details: Optional[Union[RichTextBlock, dict]] = None,
    +        output: Optional[Union[RichTextBlock, dict]] = None,
    +        sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None,
    +        status: str,  # pending, in_progress, complete, error
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a single task, representing a single action.
    +        https://docs.slack.dev/reference/block-kit/blocks/task-card-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            task_id (required): ID for the task
    +            title (required): Title of the task in plain text
    +            details: Details of the task in the form of a single "rich_text" entity.
    +            output: Output of the task in the form of a single "rich_text" entity.
    +            sources: Array of URL source elements used to generate a response.
    +            status: The state of a task. Either "pending", "in_progress", "complete", or "error".
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.task_id = task_id
    +        self.title = title
    +        self.details = details
    +        self.output = output
    +        self.sources = sources
    +        self.status = status
    +
    +    @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)")
    +    def _validate_rows(self):
    +        return self.status in ["pending", "in_progress", "complete", "error"]
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a single task, representing a single action. +https://docs.slack.dev/reference/block-kit/blocks/task-card-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    task_id : required
    +
    ID for the task
    +
    title : required
    +
    Title of the task in plain text
    +
    details
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    output
    +
    Output of the task in the form of a single "rich_text" entity.
    +
    sources
    +
    Array of URL source elements used to generate a response.
    +
    status
    +
    The state of a task. Either "pending", "in_progress", "complete", or "error".
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "task_id",
    +            "title",
    +            "details",
    +            "output",
    +            "sources",
    +            "status",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class VideoBlock (*,
    block_id: str | None = None,
    alt_text: str | None = None,
    video_url: str | None = None,
    thumbnail_url: str | None = None,
    title: str | dict | PlainTextObject | None = None,
    title_url: str | None = None,
    description: str | dict | PlainTextObject | None = None,
    provider_icon_url: str | None = None,
    provider_name: str | None = None,
    author_name: str | None = None,
    **others: dict)
    @@ -2008,6 +2264,13 @@

    PlanBlock

    + + +
  • RichTextBlock

    Class variables

    @@ -451,13 +458,13 @@

    Class variables

    Static methods

    -def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None +def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None
    -def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject]
    @@ -1271,7 +1278,7 @@

    Class variables

    Static methods

    -def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any]) +def parse(confirm: ConfirmObject | Dict[str, Any])
    @@ -1643,7 +1650,7 @@

    Class variables

    Static methods

    -def parse(filter: dict | ForwardRef('ConversationFilter')) +def parse(filter: dict | ConversationFilter)
    @@ -2835,7 +2842,7 @@

    Class variables

    Static methods

    -def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +def parse(feedback_button: FeedbackButtonObject | Dict[str, Any])
    @@ -4720,7 +4727,7 @@

    Static methods

    Creates a simple Option instance with the same value and label

    -def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None
    @@ -4918,7 +4925,7 @@

    Class variables

    Static methods

    -def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None
    @@ -5345,6 +5352,115 @@

    Inherited members

  • +
    +class PlanBlock +(*,
    title: str,
    tasks: Sequence[Dict | TaskCardBlock] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class PlanBlock(Block):
    +    type = "plan"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "title",
    +                "tasks",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        title: str,
    +        tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a collection of related tasks.
    +        https://docs.slack.dev/reference/block-kit/blocks/plan-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            title (required): Title of the plan in plain text
    +            tasks: Details of the task in the form of a single "rich_text" entity.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.title = title
    +        self.tasks = tasks
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a collection of related tasks. +https://docs.slack.dev/reference/block-kit/blocks/plan-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    title : required
    +
    Title of the plan in plain text
    +
    tasks
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "title",
    +            "tasks",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class RadioButtonsElement (*,
    action_id: str | None = None,
    options: Sequence[dict | Option] | None = None,
    initial_option: dict | Option | None = None,
    confirm: dict | ConfirmObject | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -6027,7 +6143,7 @@

    Class variables

    class RichTextInputElement -(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    +(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | RichTextBlock | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -7196,6 +7312,147 @@

    Inherited members

    +
    +class TaskCardBlock +(*,
    task_id: str,
    title: str,
    details: RichTextBlock | dict | None = None,
    output: RichTextBlock | dict | None = None,
    sources: Sequence[UrlSourceElement | dict] | None = None,
    status: str,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TaskCardBlock(Block):
    +    type = "task_card"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "task_id",
    +                "title",
    +                "details",
    +                "output",
    +                "sources",
    +                "status",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        task_id: str,
    +        title: str,
    +        details: Optional[Union[RichTextBlock, dict]] = None,
    +        output: Optional[Union[RichTextBlock, dict]] = None,
    +        sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None,
    +        status: str,  # pending, in_progress, complete, error
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a single task, representing a single action.
    +        https://docs.slack.dev/reference/block-kit/blocks/task-card-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            task_id (required): ID for the task
    +            title (required): Title of the task in plain text
    +            details: Details of the task in the form of a single "rich_text" entity.
    +            output: Output of the task in the form of a single "rich_text" entity.
    +            sources: Array of URL source elements used to generate a response.
    +            status: The state of a task. Either "pending", "in_progress", "complete", or "error".
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.task_id = task_id
    +        self.title = title
    +        self.details = details
    +        self.output = output
    +        self.sources = sources
    +        self.status = status
    +
    +    @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)")
    +    def _validate_rows(self):
    +        return self.status in ["pending", "in_progress", "complete", "error"]
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a single task, representing a single action. +https://docs.slack.dev/reference/block-kit/blocks/task-card-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    task_id : required
    +
    ID for the task
    +
    title : required
    +
    Title of the task in plain text
    +
    details
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    output
    +
    Output of the task in the form of a single "rich_text" entity.
    +
    sources
    +
    Array of URL source elements used to generate a response.
    +
    status
    +
    The state of a task. Either "pending", "in_progress", "complete", or "error".
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "task_id",
    +            "title",
    +            "details",
    +            "output",
    +            "sources",
    +            "status",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class TextObject (text: str,
    type: str | None = None,
    subtype: str | None = None,
    emoji: bool | None = None,
    **kwargs)
    @@ -7290,7 +7547,7 @@

    Class variables

    Static methods

    -def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
    default_type: str = 'mrkdwn') ‑> TextObject | None
    +def parse(text: str | Dict[str, Any] | TextObject,
    default_type: str = 'mrkdwn') ‑> TextObject | None
    @@ -7595,6 +7852,103 @@

    Inherited members

    +
    +class UrlSourceElement +(*, url: str, text: str, **others: Dict) +
    +
    +
    + +Expand source code + +
    class UrlSourceElement(BlockElement):
    +    type = "url"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "url",
    +                "text",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str,
    +        text: str,
    +        **others: Dict,
    +    ):
    +        """
    +        A URL source element that displays a URL source for referencing within a task card block.
    +        https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
    +
    +        Args:
    +            url (required): The URL type source.
    +            text (required): Display text for the URL.
    +        """
    +        super().__init__(type=self.type)
    +        show_unknown_key_warning(self, others)
    +        self.url = url
    +        self.text = text
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    A URL source element that displays a URL source for referencing within a task card block. +https://docs.slack.dev/reference/block-kit/block-elements/url-source-element

    +

    Args

    +
    +
    url : required
    +
    The URL type source.
    +
    text : required
    +
    Display text for the URL.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "url",
    +            "text",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class UserMultiSelectElement (*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_users: Sequence[str] | None = None,
    confirm: dict | ConfirmObject | None = None,
    max_selected_items: int | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -8389,6 +8743,13 @@

    PlanBlock

    + + +
  • RadioButtonsElement

    • attributes
    • @@ -8509,6 +8870,13 @@

      TaskCardBlock

      + + +
    • TextObject

      • attributes
      • @@ -8532,6 +8900,13 @@

        UrlSourceElement

        + + +
      • UserMultiSelectElement

        • attributes
        • diff --git a/docs/reference/models/index.html b/docs/reference/models/index.html index d3670bfde..d18523ae7 100644 --- a/docs/reference/models/index.html +++ b/docs/reference/models/index.html @@ -316,6 +316,7 @@

          Subclasses

        • AbstractDialogSelector
        • DialogBuilder
        • DialogTextComponent
        • +
        • Chunk
        • Message
        • ContentItemEntityFields
        • EntityActionButton
        • diff --git a/docs/reference/models/messages/chunk.html b/docs/reference/models/messages/chunk.html new file mode 100644 index 000000000..49c1ef1bc --- /dev/null +++ b/docs/reference/models/messages/chunk.html @@ -0,0 +1,447 @@ + + + + + + +slack_sdk.models.messages.chunk API documentation + + + + + + + + + + + +
          +
          +
          +

          Module slack_sdk.models.messages.chunk

          +
          +
          +
          +
          +
          +
          +
          +
          +
          +
          +

          Classes

          +
          +
          +class Chunk +(*, type: str | None = None) +
          +
          +
          + +Expand source code + +
          class Chunk(JsonObject):
          +    """
          +    Chunk for streaming messages.
          +
          +    https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +    """
          +
          +    attributes = {"type"}
          +    logger = logging.getLogger(__name__)
          +
          +    def __init__(
          +        self,
          +        *,
          +        type: Optional[str] = None,
          +    ):
          +        self.type = type
          +
          +    @classmethod
          +    def parse(cls, chunk: Union[Dict, "Chunk"]) -> Optional["Chunk"]:
          +        if chunk is None:
          +            return None
          +        elif isinstance(chunk, Chunk):
          +            return chunk
          +        else:
          +            if "type" in chunk:
          +                type = chunk["type"]
          +                if type == MarkdownTextChunk.type:
          +                    return MarkdownTextChunk(**chunk)
          +                elif type == PlanUpdateChunk.type:
          +                    return PlanUpdateChunk(**chunk)
          +                elif type == TaskUpdateChunk.type:
          +                    return TaskUpdateChunk(**chunk)
          +                else:
          +                    cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
          +                    return None
          +            else:
          +                cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
          +                return None
          +
          + +

          Ancestors

          + +

          Subclasses

          + +

          Class variables

          +
          +
          var attributes
          +
          +

          The type of the None singleton.

          +
          +
          var logger
          +
          +

          The type of the None singleton.

          +
          +
          +

          Static methods

          +
          +
          +def parse(chunk: Dict | Chunk) ‑> Chunk | None +
          +
          +
          +
          +
          +

          Inherited members

          + +
          +
          +class MarkdownTextChunk +(*, text: str, **others: Dict) +
          +
          +
          + +Expand source code + +
          class MarkdownTextChunk(Chunk):
          +    type = "markdown_text"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"text"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        text: str,
          +        **others: Dict,
          +    ):
          +        """Used for streaming text content with markdown formatting support.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.text = text
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union({"text"})
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +class PlanUpdateChunk +(*, title: str, **others: Dict) +
          +
          +
          + +Expand source code + +
          class PlanUpdateChunk(Chunk):
          +    type = "plan_update"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"title"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        title: str,
          +        **others: Dict,
          +    ):
          +        """Used for displaying an updated title of a plan.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.title = title
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union({"title"})
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +class TaskUpdateChunk +(*,
          id: str,
          title: str,
          status: str,
          details: str | None = None,
          output: str | None = None,
          sources: Sequence[Dict | UrlSourceElement] | None = None,
          **others: Dict)
          +
          +
          +
          + +Expand source code + +
          class TaskUpdateChunk(Chunk):
          +    type = "task_update"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union(
          +            {
          +                "id",
          +                "title",
          +                "status",
          +                "details",
          +                "output",
          +                "sources",
          +            }
          +        )
          +
          +    def __init__(
          +        self,
          +        *,
          +        id: str,
          +        title: str,
          +        status: str,  # "pending", "in_progress", "complete", "error"
          +        details: Optional[str] = None,
          +        output: Optional[str] = None,
          +        sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None,
          +        **others: Dict,
          +    ):
          +        """Used for displaying task progress in a timeline-style UI.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.id = id
          +        self.title = title
          +        self.status = status
          +        self.details = details
          +        self.output = output
          +        self.sources = sources
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union(
          +        {
          +            "id",
          +            "title",
          +            "status",
          +            "details",
          +            "output",
          +            "sources",
          +        }
          +    )
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +
          +
          + +
          + + + diff --git a/docs/reference/models/messages/index.html b/docs/reference/models/messages/index.html index 5d10ced1c..3139098be 100644 --- a/docs/reference/models/messages/index.html +++ b/docs/reference/models/messages/index.html @@ -40,6 +40,10 @@

          Module slack_sdk.models.messages

          Sub-modules

          +
          slack_sdk.models.messages.chunk
          +
          +
          +
          slack_sdk.models.messages.message
          @@ -262,6 +266,7 @@

          Class variables

        • Sub-modules

        • diff --git a/docs/reference/models/views/index.html b/docs/reference/models/views/index.html index d9e60359b..b7bad66bc 100644 --- a/docs/reference/models/views/index.html +++ b/docs/reference/models/views/index.html @@ -48,7 +48,7 @@

          Classes

          class View -(type: str,
          id: str | None = None,
          callback_id: str | None = None,
          external_id: str | None = None,
          team_id: str | None = None,
          bot_id: str | None = None,
          app_id: str | None = None,
          root_view_id: str | None = None,
          previous_view_id: str | None = None,
          title: str | dict | PlainTextObject | None = None,
          submit: str | dict | PlainTextObject | None = None,
          close: str | dict | PlainTextObject | None = None,
          blocks: Sequence[dict | Block] | None = None,
          private_metadata: str | None = None,
          state: dict | ForwardRef('ViewState') | None = None,
          hash: str | None = None,
          clear_on_close: bool | None = None,
          notify_on_close: bool | None = None,
          **kwargs)
          +(type: str,
          id: str | None = None,
          callback_id: str | None = None,
          external_id: str | None = None,
          team_id: str | None = None,
          bot_id: str | None = None,
          app_id: str | None = None,
          root_view_id: str | None = None,
          previous_view_id: str | None = None,
          title: str | dict | PlainTextObject | None = None,
          submit: str | dict | PlainTextObject | None = None,
          close: str | dict | PlainTextObject | None = None,
          blocks: Sequence[dict | Block] | None = None,
          private_metadata: str | None = None,
          state: dict | ViewState | None = None,
          hash: str | None = None,
          clear_on_close: bool | None = None,
          notify_on_close: bool | None = None,
          **kwargs)
          @@ -229,7 +229,7 @@

          Inherited members

          class ViewState -(*,
          values: Dict[str, Dict[str, dict | ForwardRef('ViewStateValue')]])
          +(*,
          values: Dict[str, Dict[str, dict | ViewStateValue]])
          diff --git a/docs/reference/oauth/installation_store/index.html b/docs/reference/oauth/installation_store/index.html index 4f90fb1c5..8d95802d0 100644 --- a/docs/reference/oauth/installation_store/index.html +++ b/docs/reference/oauth/installation_store/index.html @@ -193,10 +193,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: @@ -850,14 +852,18 @@

          Inherited members

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -865,7 +871,7 @@

          Inherited members

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/bot.html b/docs/reference/oauth/installation_store/models/bot.html index bb6182df2..b27abfc7d 100644 --- a/docs/reference/oauth/installation_store/models/bot.html +++ b/docs/reference/oauth/installation_store/models/bot.html @@ -150,10 +150,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/index.html b/docs/reference/oauth/installation_store/models/index.html index b96d16179..69e08affe 100644 --- a/docs/reference/oauth/installation_store/models/index.html +++ b/docs/reference/oauth/installation_store/models/index.html @@ -161,10 +161,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: @@ -469,14 +471,18 @@

          Methods

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -484,7 +490,7 @@

          Methods

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/installation.html b/docs/reference/oauth/installation_store/models/installation.html index c0de92807..3f47d901a 100644 --- a/docs/reference/oauth/installation_store/models/installation.html +++ b/docs/reference/oauth/installation_store/models/installation.html @@ -222,14 +222,18 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -237,7 +241,7 @@

          Classes

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/state_store/sqlalchemy/index.html b/docs/reference/oauth/state_store/sqlalchemy/index.html index 29fe36884..a35fa3171 100644 --- a/docs/reference/oauth/state_store/sqlalchemy/index.html +++ b/docs/reference/oauth/state_store/sqlalchemy/index.html @@ -99,7 +99,7 @@

          Classes

          async def async_issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) async with self.engine.begin() as conn: await conn.execute( self.oauth_states.insert(), @@ -111,7 +111,7 @@

          Classes

          try: async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -191,7 +191,7 @@

          Methods

          try: async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -215,7 +215,7 @@

          Methods

          async def async_issue(self, *args, **kwargs) -> str:
               state: str = str(uuid4())
          -    now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
          +    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
               async with self.engine.begin() as conn:
                   await conn.execute(
                       self.oauth_states.insert(),
          @@ -293,7 +293,7 @@ 

          Methods

          def issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) with self.engine.begin() as conn: conn.execute( self.oauth_states.insert(), @@ -305,7 +305,7 @@

          Methods

          try: with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -385,7 +385,7 @@

          Methods

          try: with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -422,7 +422,7 @@

          Methods

          def issue(self, *args, **kwargs) -> str:
               state: str = str(uuid4())
          -    now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
          +    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
               with self.engine.begin() as conn:
                   conn.execute(
                       self.oauth_states.insert(),
          diff --git a/docs/reference/web/async_chat_stream.html b/docs/reference/web/async_chat_stream.html
          index ca7bf2506..d8e4f5bfe 100644
          --- a/docs/reference/web/async_chat_stream.html
          +++ b/docs/reference/web/async_chat_stream.html
          @@ -48,7 +48,7 @@ 

          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)
          +(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,
          task_display_mode: str | None = None,
          **kwargs)
          @@ -72,6 +72,7 @@

          Classes

          buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -87,6 +88,8 @@

          Classes

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. 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. @@ -99,6 +102,7 @@

          Classes

          "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -109,7 +113,8 @@

          Classes

          async def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[AsyncSlackResponse]: """Append to the stream. @@ -118,6 +123,7 @@

          Classes

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -145,9 +151,10 @@

          Classes

          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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -163,6 +170,7 @@

          Classes

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -171,6 +179,7 @@

          Classes

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -211,26 +220,36 @@

          Classes

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + async def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = await self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -240,7 +259,7 @@

          Classes

          channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response
          @@ -266,6 +285,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          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.
          @@ -275,7 +297,7 @@

          Args

          Methods

          -async def append(self, *, markdown_text: str, **kwargs) ‑> AsyncSlackResponse | None +async def append(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse | None
          @@ -285,7 +307,8 @@

          Methods

          async def append(
               self,
               *,
          -    markdown_text: str,
          +    markdown_text: Optional[str] = None,
          +    chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
               **kwargs,
           ) -> Optional[AsyncSlackResponse]:
               """Append to the stream.
          @@ -294,6 +317,7 @@ 

          Methods

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -321,9 +345,10 @@

          Methods

          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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -340,6 +365,8 @@

          Methods

          is stopped this method cannot be called.

          Args

          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          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.
          @@ -366,7 +393,7 @@

          Example

          -async def stop(self,
          *,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def stop(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -377,6 +404,7 @@

          Example

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -385,6 +413,7 @@

          Example

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -425,12 +454,17 @@

          Example

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) @@ -442,6 +476,8 @@

          Args

          blocks
          A list of blocks that will be rendered at the bottom of the finalized message.
          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          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.
          diff --git a/docs/reference/web/async_client.html b/docs/reference/web/async_client.html index 2ec1ba84a..070de0e95 100644 --- a/docs/reference/web/async_client.html +++ b/docs/reference/web/async_client.html @@ -2651,7 +2651,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -2662,8 +2663,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs) @@ -2905,6 +2908,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -2917,8 +2922,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs) @@ -2930,6 +2938,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -2942,6 +2951,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2956,6 +2966,7 @@

          Classes

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -2982,6 +2993,8 @@

          Classes

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3007,6 +3020,7 @@

          Classes

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10144,7 +10158,7 @@

          Methods

          Unarchives a channel.

          -async def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> AsyncSlackResponse +async def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10156,7 +10170,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -10167,8 +10182,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs)
          @@ -10494,7 +10511,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -async def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10509,6 +10526,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -10521,8 +10540,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs)
          @@ -10530,7 +10552,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -async def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10545,6 +10567,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -10557,6 +10580,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10567,7 +10591,7 @@

          Methods

          https://docs.slack.dev/reference/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
          +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,
          task_display_mode: str | None = None,
          **kwargs) ‑> AsyncChatStream
          @@ -10582,6 +10606,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -10608,6 +10633,8 @@

          Methods

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10633,6 +10660,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, )
          @@ -10665,6 +10693,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/chat_stream.html b/docs/reference/web/chat_stream.html index 94d96e5eb..59fcbbd84 100644 --- a/docs/reference/web/chat_stream.html +++ b/docs/reference/web/chat_stream.html @@ -48,7 +48,7 @@

          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)
          +(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,
          task_display_mode: str | None = None,
          **kwargs)
          @@ -72,6 +72,7 @@

          Classes

          buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -87,6 +88,8 @@

          Classes

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. 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. @@ -99,6 +102,7 @@

          Classes

          "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -109,7 +113,8 @@

          Classes

          def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[SlackResponse]: """Append to the stream. @@ -118,6 +123,7 @@

          Classes

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -145,9 +151,10 @@

          Classes

          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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -163,6 +170,7 @@

          Classes

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -171,6 +179,7 @@

          Classes

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -211,26 +220,36 @@

          Classes

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - def _flush_buffer(self, **kwargs) -> SlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> SlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -240,7 +259,7 @@

          Classes

          channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response @@ -266,6 +285,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          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.
          @@ -275,7 +297,7 @@

          Args

          Methods

          -def append(self, *, markdown_text: str, **kwargs) ‑> SlackResponse | None +def append(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse | None
          @@ -285,7 +307,8 @@

          Methods

          def append(
               self,
               *,
          -    markdown_text: str,
          +    markdown_text: Optional[str] = None,
          +    chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
               **kwargs,
           ) -> Optional[SlackResponse]:
               """Append to the stream.
          @@ -294,6 +317,7 @@ 

          Methods

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -321,9 +345,10 @@

          Methods

          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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -340,6 +365,8 @@

          Methods

          is stopped this method cannot be called.

          Args

          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          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.
          @@ -366,7 +393,7 @@

          Example

          -def stop(self,
          *,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def stop(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          @@ -377,6 +404,7 @@

          Example

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -385,6 +413,7 @@

          Example

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -425,12 +454,17 @@

          Example

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) @@ -442,6 +476,8 @@

          Args

          blocks
          A list of blocks that will be rendered at the bottom of the finalized message.
          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          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.
          diff --git a/docs/reference/web/client.html b/docs/reference/web/client.html index 6fded11e1..fecd86b7c 100644 --- a/docs/reference/web/client.html +++ b/docs/reference/web/client.html @@ -2651,7 +2651,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2662,8 +2663,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2905,6 +2908,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -2917,8 +2922,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2930,6 +2938,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -2942,6 +2951,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2956,6 +2966,7 @@

          Classes

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -2982,6 +2993,8 @@

          Classes

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3007,6 +3020,7 @@

          Classes

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10144,7 +10158,7 @@

          Methods

          Unarchives a channel.

          -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10156,7 +10170,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10167,8 +10182,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
          @@ -10494,7 +10511,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10509,6 +10526,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10521,8 +10540,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
          @@ -10530,7 +10552,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10545,6 +10567,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10557,6 +10580,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10567,7 +10591,7 @@

          Methods

          https://docs.slack.dev/reference/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
          +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,
          task_display_mode: str | None = None,
          **kwargs) ‑> ChatStream
          @@ -10582,6 +10606,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10608,6 +10633,8 @@

          Methods

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10633,6 +10660,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10665,6 +10693,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/index.html b/docs/reference/web/index.html index 611a26b3b..bec63590c 100644 --- a/docs/reference/web/index.html +++ b/docs/reference/web/index.html @@ -3020,7 +3020,8 @@

          Raises

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -3031,8 +3032,10 @@

          Raises

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -3274,6 +3277,8 @@

          Raises

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -3286,8 +3291,11 @@

          Raises

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -3299,6 +3307,7 @@

          Raises

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -3311,6 +3320,7 @@

          Raises

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -3325,6 +3335,7 @@

          Raises

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -3351,6 +3362,8 @@

          Raises

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3376,6 +3389,7 @@

          Raises

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10513,7 +10527,7 @@

          Methods

          Unarchives a channel.

          -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10525,7 +10539,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10536,8 +10551,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
          @@ -10863,7 +10880,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10878,6 +10895,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10890,8 +10909,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
          @@ -10899,7 +10921,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10914,6 +10936,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10926,6 +10949,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10936,7 +10960,7 @@

          Methods

          https://docs.slack.dev/reference/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
          +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,
          task_display_mode: str | None = None,
          **kwargs) ‑> ChatStream
          @@ -10951,6 +10975,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10977,6 +11002,8 @@

          Methods

          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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -11002,6 +11029,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -11034,6 +11062,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/legacy_client.html b/docs/reference/web/legacy_client.html index d100178dc..86d9b1d0c 100644 --- a/docs/reference/web/legacy_client.html +++ b/docs/reference/web/legacy_client.html @@ -2650,7 +2650,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -2661,8 +2662,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2904,6 +2907,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -2916,8 +2921,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2929,6 +2937,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -2941,6 +2950,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10080,7 +10090,7 @@

          Methods

          Unarchives a channel.

  • -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +def chat_appendStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10092,7 +10102,8 @@

    Methods

    *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -10103,8 +10114,10 @@

    Methods

    "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
    @@ -10430,7 +10443,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

    -def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    task_display_mode: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10445,6 +10458,8 @@

    Methods

    markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -10457,8 +10472,11 @@

    Methods

    "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
    @@ -10466,7 +10484,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.startStream

    -def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10481,6 +10499,7 @@

    Methods

    markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -10493,6 +10512,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index d2776a9dc..b8592c2e9 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -55,6 +55,7 @@ StaticSelectElement, TimePickerElement, UrlInputElement, + UrlSourceElement, UserMultiSelectElement, UserSelectElement, ) @@ -70,9 +71,11 @@ ImageBlock, InputBlock, MarkdownBlock, + PlanBlock, RichTextBlock, SectionBlock, TableBlock, + TaskCardBlock, VideoBlock, ) @@ -111,6 +114,7 @@ "PlainTextInputElement", "EmailInputElement", "UrlInputElement", + "UrlSourceElement", "NumberInputElement", "RadioButtonsElement", "SelectElement", @@ -135,8 +139,10 @@ "ImageBlock", "InputBlock", "MarkdownBlock", + "PlanBlock", "SectionBlock", "TableBlock", + "TaskCardBlock", "VideoBlock", "RichTextBlock", ] diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py index 89f0a7994..bcf7adfe0 100644 --- a/slack_sdk/models/blocks/block_elements.py +++ b/slack_sdk/models/blocks/block_elements.py @@ -1654,6 +1654,44 @@ def __init__( self.dispatch_action_config = dispatch_action_config +# ------------------------------------------------- +# Url Source Element +# ------------------------------------------------- + + +class UrlSourceElement(BlockElement): + type = "url" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "url", + "text", + } + ) + + def __init__( + self, + *, + url: str, + text: str, + **others: Dict, + ): + """ + A URL source element that displays a URL source for referencing within a task card block. + https://docs.slack.dev/reference/block-kit/block-elements/url-source-element + + Args: + url (required): The URL type source. + text (required): Display text for the URL. + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + self.url = url + self.text = text + + # ------------------------------------------------- # Number Input Element # ------------------------------------------------- diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index cac463c99..bcd7efd6e 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -16,6 +16,7 @@ InputInteractiveElement, InteractiveElement, RichTextElement, + UrlSourceElement, ) # ------------------------------------------------- @@ -97,6 +98,10 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]: return RichTextBlock(**block) elif type == TableBlock.type: return TableBlock(**block) + elif type == TaskCardBlock.type: + return TaskCardBlock(**block) + elif type == PlanBlock.type: + return PlanBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -777,3 +782,99 @@ def __init__( @JsonValidator("rows attribute must be specified") def _validate_rows(self): return self.rows is not None and len(self.rows) > 0 + + +class TaskCardBlock(Block): + type = "task_card" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "task_id", + "title", + "details", + "output", + "sources", + "status", + } + ) + + def __init__( + self, + *, + task_id: str, + title: str, + details: Optional[Union[RichTextBlock, dict]] = None, + output: Optional[Union[RichTextBlock, dict]] = None, + sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None, + status: str, # pending, in_progress, complete, error + block_id: Optional[str] = None, + **others: dict, + ): + """Displays a single task, representing a single action. + https://docs.slack.dev/reference/block-kit/blocks/task-card-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + task_id (required): ID for the task + title (required): Title of the task in plain text + details: Details of the task in the form of a single "rich_text" entity. + output: Output of the task in the form of a single "rich_text" entity. + sources: Array of URL source elements used to generate a response. + status: The state of a task. Either "pending", "in_progress", "complete", or "error". + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.task_id = task_id + self.title = title + self.details = details + self.output = output + self.sources = sources + self.status = status + + @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)") + def _validate_rows(self): + return self.status in ["pending", "in_progress", "complete", "error"] + + +class PlanBlock(Block): + type = "plan" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "title", + "tasks", + } + ) + + def __init__( + self, + *, + title: str, + tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None, + block_id: Optional[str] = None, + **others: dict, + ): + """Displays a collection of related tasks. + https://docs.slack.dev/reference/block-kit/blocks/plan-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + title (required): Title of the plan in plain text + tasks: A sequence of task card blocks. Each task represents a single action within the plan. + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.title = title + self.tasks = tasks diff --git a/slack_sdk/models/messages/chunk.py b/slack_sdk/models/messages/chunk.py new file mode 100644 index 000000000..657d95ae4 --- /dev/null +++ b/slack_sdk/models/messages/chunk.py @@ -0,0 +1,134 @@ +import logging +from typing import Dict, Optional, Sequence, Set, Union + +from slack_sdk.models import show_unknown_key_warning +from slack_sdk.models.basic_objects import JsonObject +from slack_sdk.models.blocks.block_elements import UrlSourceElement + + +class Chunk(JsonObject): + """ + Chunk for streaming messages. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + + attributes = {"type"} + logger = logging.getLogger(__name__) + + def __init__( + self, + *, + type: Optional[str] = None, + ): + self.type = type + + @classmethod + def parse(cls, chunk: Union[Dict, "Chunk"]) -> Optional["Chunk"]: + if chunk is None: + return None + elif isinstance(chunk, Chunk): + return chunk + else: + if "type" in chunk: + type = chunk["type"] + if type == MarkdownTextChunk.type: + return MarkdownTextChunk(**chunk) + elif type == PlanUpdateChunk.type: + return PlanUpdateChunk(**chunk) + elif type == TaskUpdateChunk.type: + return TaskUpdateChunk(**chunk) + else: + cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})") + return None + else: + cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})") + return None + + +class MarkdownTextChunk(Chunk): + type = "markdown_text" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"text"}) + + def __init__( + self, + *, + text: str, + **others: Dict, + ): + """Used for streaming text content with markdown formatting support. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.text = text + + +class PlanUpdateChunk(Chunk): + type = "plan_update" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"title"}) + + def __init__( + self, + *, + title: str, + **others: Dict, + ): + """Used for displaying an updated title of a plan. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.title = title + + +class TaskUpdateChunk(Chunk): + type = "task_update" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "id", + "title", + "status", + "details", + "output", + "sources", + } + ) + + def __init__( + self, + *, + id: str, + title: str, + status: str, # "pending", "in_progress", "complete", "error" + details: Optional[str] = None, + output: Optional[str] = None, + sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None, + **others: Dict, + ): + """Used for displaying task progress in a timeline-style UI. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.id = id + self.title = title + self.status = status + self.details = details + self.output = output + self.sources = sources diff --git a/slack_sdk/version.py b/slack_sdk/version.py index fb572e0ba..7f81a760a 100644 --- a/slack_sdk/version.py +++ b/slack_sdk/version.py @@ -1,3 +1,3 @@ """Check the latest version at https://pypi.org/project/slack-sdk/""" -__version__ = "3.39.0" +__version__ = "3.40.0.dev0" diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py index 4661f19dd..7348b90bc 100644 --- a/slack_sdk/web/async_chat_stream.py +++ b/slack_sdk/web/async_chat_stream.py @@ -10,10 +10,11 @@ import json import logging -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.messages.chunk import Chunk, MarkdownTextChunk from slack_sdk.models.metadata import Metadata from slack_sdk.web.async_slack_response import AsyncSlackResponse @@ -38,6 +39,7 @@ def __init__( buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -53,6 +55,8 @@ def __init__( 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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. 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. @@ -65,6 +69,7 @@ def __init__( "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -75,7 +80,8 @@ def __init__( async def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[AsyncSlackResponse]: """Append to the stream. @@ -84,6 +90,7 @@ async def append( is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -111,9 +118,10 @@ async def append( 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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -129,6 +137,7 @@ async def stop( self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -137,6 +146,7 @@ async def stop( Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -177,26 +187,36 @@ async def stop( raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + async def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = await self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -206,7 +226,7 @@ async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index ca163da98..e96a4f6d5 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -17,12 +17,13 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk 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, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .async_base_client import AsyncBaseClient, AsyncSlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2630,7 +2631,8 @@ async def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -2641,8 +2643,10 @@ async def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs) @@ -2884,6 +2888,8 @@ async def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -2896,8 +2902,11 @@ async def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs) @@ -2909,6 +2918,7 @@ async def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -2921,6 +2931,7 @@ async def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2935,6 +2946,7 @@ async def chat_stream( thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -2961,6 +2973,8 @@ async def chat_stream( 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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -2986,6 +3000,7 @@ async def chat_stream( thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py index 1a379c9cb..683859490 100644 --- a/slack_sdk/web/chat_stream.py +++ b/slack_sdk/web/chat_stream.py @@ -1,9 +1,10 @@ import json import logging -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.messages.chunk import Chunk, MarkdownTextChunk from slack_sdk.models.metadata import Metadata from slack_sdk.web.slack_response import SlackResponse @@ -28,6 +29,7 @@ def __init__( buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -43,6 +45,8 @@ def __init__( 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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. 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. @@ -55,6 +59,7 @@ def __init__( "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -65,7 +70,8 @@ def __init__( def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[SlackResponse]: """Append to the stream. @@ -74,6 +80,7 @@ def append( is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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. @@ -101,9 +108,10 @@ def append( 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) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -119,6 +127,7 @@ def stop( self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -127,6 +136,7 @@ def stop( Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. 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 @@ -167,26 +177,36 @@ def stop( raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - def _flush_buffer(self, **kwargs) -> SlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> SlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -196,7 +216,7 @@ def _flush_buffer(self, **kwargs) -> SlackResponse: channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index dfa771832..200b216ff 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -7,12 +7,13 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk 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, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .base_client import BaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2620,7 +2621,8 @@ def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2631,8 +2633,10 @@ def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2874,6 +2878,8 @@ def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -2886,8 +2892,11 @@ def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2899,6 +2908,7 @@ def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -2911,6 +2921,7 @@ def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2925,6 +2936,7 @@ def chat_stream( thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -2951,6 +2963,8 @@ def chat_stream( 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. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -2976,6 +2990,7 @@ def chat_stream( thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) diff --git a/slack_sdk/web/internal_utils.py b/slack_sdk/web/internal_utils.py index 87139559c..ad23f87f8 100644 --- a/slack_sdk/web/internal_utils.py +++ b/slack_sdk/web/internal_utils.py @@ -11,13 +11,14 @@ from ssl import SSLContext from typing import Any, Dict, Optional, Sequence, Union from urllib.parse import urljoin -from urllib.request import OpenerDirector, ProxyHandler, HTTPSHandler, Request, urlopen +from urllib.request import HTTPSHandler, OpenerDirector, ProxyHandler, Request, urlopen from slack_sdk import version from slack_sdk.errors import SlackRequestError from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block -from slack_sdk.models.metadata import Metadata, EventAndEntityMetadata, EntityMetadata +from slack_sdk.models.messages.chunk import Chunk +from slack_sdk.models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata def convert_bool_to_0_or_1(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: @@ -187,11 +188,13 @@ def _build_req_args( def _parse_web_class_objects(kwargs) -> None: - def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata, EntityMetadata]): + def to_dict(obj: Union[Dict, Block, Attachment, Chunk, Metadata, EventAndEntityMetadata, EntityMetadata]): if isinstance(obj, Block): return obj.to_dict() if isinstance(obj, Attachment): return obj.to_dict() + if isinstance(obj, Chunk): + return obj.to_dict() if isinstance(obj, Metadata): return obj.to_dict() if isinstance(obj, EventAndEntityMetadata): @@ -211,6 +214,11 @@ def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata dict_attachments = [to_dict(a) for a in attachments] kwargs.update({"attachments": dict_attachments}) + chunks = kwargs.get("chunks", None) + if chunks is not None and isinstance(chunks, Sequence) and (not isinstance(chunks, str)): + dict_chunks = [to_dict(c) for c in chunks] + kwargs.update({"chunks": dict_chunks}) + metadata = kwargs.get("metadata", None) if metadata is not None and ( isinstance(metadata, Metadata) diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index df2bcc370..061be7c85 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -19,11 +19,12 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk from slack_sdk.models.views import View from ..models.attachments import Attachment from ..models.blocks import Block, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .legacy_base_client import LegacyBaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2631,7 +2632,8 @@ def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -2642,8 +2644,10 @@ def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2885,6 +2889,8 @@ def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -2897,8 +2903,11 @@ def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2910,6 +2919,7 @@ def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -2922,6 +2932,7 @@ def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index 6f3b9f141..531ebe057 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -21,6 +21,7 @@ Option, OverflowMenuElement, PlainTextObject, + PlanBlock, RawTextObject, RichTextBlock, RichTextElementParts, @@ -31,6 +32,7 @@ SectionBlock, StaticSelectElement, TableBlock, + TaskCardBlock, VideoBlock, ) from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile @@ -890,6 +892,86 @@ def test_text_length_12001(self): MarkdownBlock(**input).validate_json() +# ---------------------------------------------- +# Plan +# ---------------------------------------------- + + +class PlanBlockTests(unittest.TestCase): + def test_document(self): + input = { + "type": "plan", + "title": "Thinking completed", + "tasks": [ + { + "task_id": "call_001", + "title": "Fetched user profile information", + "status": "in_progress", + "details": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "Searched database..."}]} + ], + }, + "output": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "Profile data loaded"}]} + ], + }, + }, + { + "task_id": "call_002", + "title": "Checked user permissions", + "status": "pending", + }, + { + "task_id": "call_003", + "title": "Generated comprehensive user report", + "status": "complete", + "output": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "15 data points compiled"}]} + ], + }, + }, + ], + } + self.assertDictEqual(input, PlanBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + +# ---------------------------------------------- +# Task card +# ---------------------------------------------- + + +class TaskCardBlockTests(unittest.TestCase): + def test_document(self): + input = { + "type": "task_card", + "task_id": "task_1", + "title": "Fetching weather data", + "status": "pending", + "output": { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "Found weather data for Chicago from 2 sources"}], + } + ], + }, + "sources": [ + {"type": "url", "url": "https://weather.com/", "text": "weather.com"}, + {"type": "url", "url": "https://www.accuweather.com/", "text": "accuweather.com"}, + ], + } + self.assertDictEqual(input, TaskCardBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + # ---------------------------------------------- # Video # ---------------------------------------------- diff --git a/tests/slack_sdk/models/test_chunks.py b/tests/slack_sdk/models/test_chunks.py new file mode 100644 index 000000000..78845b307 --- /dev/null +++ b/tests/slack_sdk/models/test_chunks.py @@ -0,0 +1,91 @@ +import unittest + +from slack_sdk.models.blocks.block_elements import UrlSourceElement +from slack_sdk.models.messages.chunk import MarkdownTextChunk, PlanUpdateChunk, TaskUpdateChunk + + +class MarkdownTextChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + MarkdownTextChunk(text="greetings!").to_dict(), + { + "type": "markdown_text", + "text": "greetings!", + }, + ) + + +class PlanUpdateChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + PlanUpdateChunk(title="Crunching numbers...").to_dict(), + { + "type": "plan_update", + "title": "Crunching numbers...", + }, + ) + + +class TaskUpdateChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + TaskUpdateChunk(id="001", title="Waiting...", status="pending").to_dict(), + { + "type": "task_update", + "id": "001", + "title": "Waiting...", + "status": "pending", + }, + ) + self.assertDictEqual( + TaskUpdateChunk( + id="002", + title="Wondering...", + status="in_progress", + details="- Gathering information...", + ).to_dict(), + { + "type": "task_update", + "id": "002", + "title": "Wondering...", + "status": "in_progress", + "details": "- Gathering information...", + }, + ) + self.assertDictEqual( + TaskUpdateChunk( + id="003", + title="Answering...", + status="complete", + output="Found a solution", + sources=[ + UrlSourceElement( + text="Discussion of Life's Questions", + url="https://www.answers.com", + ), + UrlSourceElement( + text="The Free Encyclopedia", + url="https://wikipedia.org", + ), + ], + ).to_dict(), + { + "type": "task_update", + "id": "003", + "title": "Answering...", + "status": "complete", + "output": "Found a solution", + "sources": [ + { + "type": "url", + "text": "Discussion of Life's Questions", + "url": "https://www.answers.com", + }, + { + "type": "url", + "text": "The Free Encyclopedia", + "url": "https://wikipedia.org", + }, + ], + }, + ) diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py index 75c13c8c2..0a11b9d53 100644 --- a/tests/slack_sdk/web/test_chat_stream.py +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -7,6 +7,7 @@ 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.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk 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 @@ -105,7 +106,10 @@ def test_streams_a_short_message(self): 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!") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "nice!", "type": "markdown_text"}]', + ) def test_streams_a_long_message(self): streamer = self.client.chat_stream( @@ -146,13 +150,19 @@ def test_streams_a_long_message(self): 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( + json.dumps(start_request.get("chunks")), + '[{"text": "**this messag", "type": "markdown_text"}]', + ) 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( + json.dumps(append_request.get("chunks")), + '[{"text": "e is bold!", "type": "markdown_text"}]', + ) self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") self.assertEqual(append_request.get("ts"), "123.123") @@ -162,10 +172,76 @@ def test_streams_a_long_message(self): '[{"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( + json.dumps(stop_request.get("chunks")), + '[{"text": "**", "type": "markdown_text"}]', + ) self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") self.assertEqual(stop_request.get("ts"), "123.123") + def test_streams_a_chunk_message(self): + streamer = self.client.chat_stream( + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + task_display_mode="timeline", + ) + streamer.append(markdown_text="**this is ") + streamer.append(markdown_text="buffered**") + streamer.append( + chunks=[ + TaskUpdateChunk( + id="001", + title="Counting...", + status="pending", + ), + ], + ) + streamer.append( + chunks=[ + MarkdownTextChunk(text="**this is unbuffered**"), + ], + ) + streamer.append(markdown_text="\n") + streamer.stop( + chunks=[ + MarkdownTextChunk(text=":space_invader:"), + ], + ) + + 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( + json.dumps(start_request.get("chunks")), + '[{"text": "**this is buffered**", "type": "markdown_text"}, {"id": "001", "status": "pending", "title": "Counting...", "type": "task_update"}]', + ) + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + self.assertEqual(start_request.get("task_display_mode"), "timeline") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("ts"), "123.123") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "**this is unbuffered**", "type": "markdown_text"}]', + ) + + 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( + json.dumps(stop_request.get("chunks")), + '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', + ) + def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = self.client.chat_stream( channel="C0123456789", diff --git a/tests/slack_sdk/web/test_internal_utils.py b/tests/slack_sdk/web/test_internal_utils.py index ac7704b30..3e44f0c9c 100644 --- a/tests/slack_sdk/web/test_internal_utils.py +++ b/tests/slack_sdk/web/test_internal_utils.py @@ -2,18 +2,17 @@ import unittest from io import BytesIO from pathlib import Path -from typing import Dict, Sequence, Union - -import pytest +from typing import Dict from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block, DividerBlock +from slack_sdk.models.messages.chunk import MarkdownTextChunk, PlanUpdateChunk, TaskUpdateChunk from slack_sdk.web.internal_utils import ( _build_unexpected_body_error_message, + _get_url, + _next_cursor_is_present, _parse_web_class_objects, _to_v2_file_upload_item, - _next_cursor_is_present, - _get_url, ) @@ -57,6 +56,25 @@ def test_can_parse_sequence_of_attachments(self): for attachment in kwargs["attachments"]: assert isinstance(attachment, Dict) + def test_can_parse_sequence_of_chunks(self): + for chunks in [ + [ + MarkdownTextChunk(text="fiz"), + PlanUpdateChunk(title="fuz"), + TaskUpdateChunk(id="001", title="baz", status="complete"), + ], # list + ( + MarkdownTextChunk(text="fiz"), + PlanUpdateChunk(title="fuz"), + TaskUpdateChunk(id="001", title="baz", status="complete"), + ), # tuple + ]: + kwargs = {"chunks": chunks} + _parse_web_class_objects(kwargs) + assert kwargs["chunks"] + for chunks in kwargs["chunks"]: + assert isinstance(chunks, Dict) + def test_can_parse_str_blocks(self): input = json.dumps([Block(block_id="42").to_dict(), Block(block_id="24").to_dict()]) kwargs = {"blocks": input} @@ -71,6 +89,19 @@ def test_can_parse_str_attachments(self): assert isinstance(kwargs["attachments"], str) assert input == kwargs["attachments"] + def test_can_parse_str_chunks(self): + input = json.dumps( + [ + MarkdownTextChunk(text="fiz").to_dict(), + PlanUpdateChunk(title="fuz").to_dict(), + TaskUpdateChunk(id="001", title="baz", status="complete").to_dict(), + ] + ) + kwargs = {"chunks": input} + _parse_web_class_objects(kwargs) + assert isinstance(kwargs["chunks"], str) + assert input == kwargs["chunks"] + def test_can_parse_user_auth_blocks(self): kwargs = { "channel": "C12345", diff --git a/tests/slack_sdk_async/web/test_async_chat_stream.py b/tests/slack_sdk_async/web/test_async_chat_stream.py index 212fee1e2..2a4f5b931 100644 --- a/tests/slack_sdk_async/web/test_async_chat_stream.py +++ b/tests/slack_sdk_async/web/test_async_chat_stream.py @@ -6,6 +6,7 @@ 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.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk 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 @@ -107,7 +108,10 @@ async def test_streams_a_short_message(self): 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!") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "nice!", "type": "markdown_text"}]', + ) @async_test async def test_streams_a_long_message(self): @@ -149,13 +153,19 @@ async def test_streams_a_long_message(self): 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( + json.dumps(start_request.get("chunks")), + '[{"text": "**this messag", "type": "markdown_text"}]', + ) 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( + json.dumps(append_request.get("chunks")), + '[{"text": "e is bold!", "type": "markdown_text"}]', + ) self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") self.assertEqual(append_request.get("ts"), "123.123") @@ -165,10 +175,75 @@ async def test_streams_a_long_message(self): '[{"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( + json.dumps(stop_request.get("chunks")), + '[{"text": "**", "type": "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_a_chunk_message(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="**this is ") + await streamer.append(markdown_text="buffered**") + await streamer.append( + chunks=[ + TaskUpdateChunk( + id="001", + title="Counting...", + status="pending", + ), + ], + ) + await streamer.append( + chunks=[ + MarkdownTextChunk(text="**this is unbuffered**"), + ], + ) + await streamer.append(markdown_text="\n") + await streamer.stop( + chunks=[ + MarkdownTextChunk(text=":space_invader:"), + ], + ) + + 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( + json.dumps(start_request.get("chunks")), + '[{"text": "**this is buffered**", "type": "markdown_text"}, {"id": "001", "status": "pending", "title": "Counting...", "type": "task_update"}]', + ) + 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("ts"), "123.123") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "**this is unbuffered**", "type": "markdown_text"}]', + ) + + 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( + json.dumps(stop_request.get("chunks")), + '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', + ) + @async_test async def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = await self.client.chat_stream(