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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions slack_sdk/models/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

from .basic_components import (
ActionCellObject,
ButtonStyles,
ConfirmObject,
DynamicSelectElementTypes,
Expand Down Expand Up @@ -72,6 +73,7 @@
ContainerBlock,
ContextActionsBlock,
ContextBlock,
DataTableBlock,
DividerBlock,
FileBlock,
HeaderBlock,
Expand All @@ -87,6 +89,7 @@
)

__all__ = [
"ActionCellObject",
"ButtonStyles",
"ConfirmObject",
"DynamicSelectElementTypes",
Expand Down Expand Up @@ -147,6 +150,7 @@
"ContainerBlock",
"ContextActionsBlock",
"ContextBlock",
"DataTableBlock",
"DividerBlock",
"FileBlock",
"HeaderBlock",
Expand Down
65 changes: 64 additions & 1 deletion slack_sdk/models/blocks/basic_components.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import copy
import logging
import warnings
from typing import Any, Dict, List, Optional, Sequence, Set, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, Union

from slack_sdk.models import show_unknown_key_warning
from slack_sdk.models.basic_objects import JsonObject, JsonValidator
from slack_sdk.models.messages import Link

if TYPE_CHECKING:
from .block_elements import ButtonElement

ButtonStyles = {"danger", "primary"}
DynamicSelectElementTypes = {"channels", "conversations", "users"}

Expand Down Expand Up @@ -151,6 +154,66 @@ def direct_from_link(link: Link, title: str = "") -> Dict[str, Any]:
return MarkdownTextObject.from_link(link, title).to_dict()


class ActionCellObject(JsonObject):
"""action_cell typed object."""

type = "action_cell"
attributes = {"element", "fallback", "type"}
logger = logging.getLogger(__name__)

@classmethod
def parse(cls, cell: Union["ActionCellObject", Dict[str, Any]]) -> Optional["ActionCellObject"]:
if cell:
if isinstance(cell, ActionCellObject):
return cell
elif isinstance(cell, dict):
return ActionCellObject(**cell)
else:
cls.logger.warning(f"Unknown type ({type(cell)}) detected when parsing an ActionCellObject")
return None

def __init__(
self,
*,
element: Union[Dict[str, Any], "ButtonElement"],
fallback: Optional[Union[Dict[str, Any], "RawNumberObject", "RawTextObject"]] = None,
**others: dict,
):
"""Defines an object containing a button element. Action cells can only be used in the data table block.

https://docs.slack.dev/reference/block-kit/blocks/data-table-block

Args:
element (required): A button element.
fallback: A raw_text or raw_number cell.
"""
# Imported here since block_elements imports this module
from .block_elements import BlockElement

show_unknown_key_warning(self, others)
self.element = BlockElement.parse(element)
self.fallback = fallback
if isinstance(fallback, dict):
d = copy.copy(fallback)
t = d.pop("type", None)
if t == RawNumberObject.type:
self.fallback = RawNumberObject(**d)
elif t == RawTextObject.type:
self.fallback = RawTextObject(**d)

@JsonValidator("element attribute must be specified")
def _validate_element(self):
return self.element is not None

@JsonValidator("element attribute must be a button element")
def _validate_element_type(self):
return self.element is None or getattr(self.element, "type", None) == "button"

@JsonValidator("fallback attribute must be a raw_text or raw_number cell")
def _validate_fallback(self):
return self.fallback is None or isinstance(self.fallback, (RawNumberObject, RawTextObject))


class RawNumberObject(JsonObject):
"""raw_number typed object."""

Expand Down
64 changes: 64 additions & 0 deletions slack_sdk/models/blocks/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from ...errors import SlackObjectFormationError
from .basic_components import (
ActionCellObject,
MarkdownTextObject,
PlainTextObject,
RawNumberObject,
Expand Down Expand Up @@ -121,6 +122,8 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]:
return CarouselBlock(**block)
elif type == AuthProviderBlock.type:
return AuthProviderBlock(**block)
elif type == DataTableBlock.type:
return DataTableBlock(**block)
else:
cls.logger.warning(f"Unknown block detected and skipped ({block})")
return None
Expand Down Expand Up @@ -1300,3 +1303,64 @@ def _validate_disconnect_url_length(self):
@JsonValidator("accessory attribute must be an overflow menu element")
def _validate_accessory(self):
return self.accessory is None or isinstance(self.accessory, OverflowMenuElement)


class DataTableBlock(Block):
type = "data_table"
page_size_min = 1
page_size_max = 100

@property
def attributes(self) -> Set[str]: # type: ignore[override]
return super().attributes.union({"rows", "caption", "page_size", "row_header_column_index"})

def __init__(
self,
*,
rows: Sequence[Sequence[Union[ActionCellObject, RawTextObject, RawNumberObject, RichTextBlock, Dict[str, Any]]]],
caption: str,
page_size: Optional[int] = None,
row_header_column_index: Optional[int] = None,
block_id: Optional[str] = None,
**others: dict,
):
"""Displays rich tables that support pagination, sorting, filtering, and interactivity.

https://docs.slack.dev/reference/block-kit/blocks/data-table-block

Args:
rows (required): An array consisting of table rows.
caption (required): A caption for the table; used as the value for the HTML caption element.
page_size: Number of rows per page. Min 1, Max 100. Defaults to 5 if omitted.
row_header_column_index: The 0-based index of the column that uniquely identifies each row
(the row header). This column is treated as the row's primary identifier for screen readers.
Defaults to 0 if omitted.
block_id: A unique identifier for a block. If not specified, a block_id will be generated.
"""
super().__init__(type=self.type, block_id=block_id)
show_unknown_key_warning(self, others)

self.rows = [[self._parse_cell(cell) for cell in row] for row in rows] if rows is not None else None
self.caption = caption
self.page_size = page_size
self.row_header_column_index = row_header_column_index

@staticmethod
def _parse_cell(
cell: Union[ActionCellObject, RawTextObject, RawNumberObject, RichTextBlock, Dict[str, Any]],
) -> Union[ActionCellObject, RawTextObject, RawNumberObject, RichTextBlock, Dict[str, Any], None]:
if isinstance(cell, dict) and cell.get("type") == ActionCellObject.type:
return ActionCellObject.parse(cell)
return cell

@JsonValidator("rows attribute must be specified")
def _validate_rows(self):
return self.rows is not None and len(self.rows) > 0

@JsonValidator("caption attribute must be specified")
def _validate_caption(self):
return self.caption is not None

@JsonValidator(f"page_size must be between {page_size_min} and {page_size_max}")
def _validate_page_size(self):
return self.page_size is None or self.page_size_min <= self.page_size <= self.page_size_max
Loading
Loading