Skip to content
4 changes: 4 additions & 0 deletions slack_sdk/models/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .basic_components import (
ButtonStyles,
ColumnSettings,
ConfirmObject,
DynamicSelectElementTypes,
FeedbackButtonObject,
Expand All @@ -17,6 +18,7 @@
OptionGroup,
PlainTextObject,
RawTextObject,
RichTextCell,
TextObject,
)
from .block_elements import (
Expand Down Expand Up @@ -84,6 +86,7 @@

__all__ = [
"ButtonStyles",
"ColumnSettings",
"ConfirmObject",
"DynamicSelectElementTypes",
"FeedbackButtonObject",
Expand All @@ -92,6 +95,7 @@
"OptionGroup",
"PlainTextObject",
"RawTextObject",
"RichTextCell",
"TextObject",
"BlockElement",
"ButtonElement",
Expand Down
78 changes: 78 additions & 0 deletions slack_sdk/models/blocks/basic_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,84 @@ def _validate_text_min_length(self):
return len(self.text) >= 1


class ColumnSettings(JsonObject):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class ColumnSettings(JsonObject):
class TableBlockColumnSettings(JsonObject):

📣 note: Hoping to namespace this to match the @slack/types package too but also to avoid confusion on which columns this settings are applied:

https://github.com/slackapi/node-slack-sdk/blob/122865134ffa20ad080fcf447cfb93b7252da157/packages/types/src/block-kit/blocks.ts#L469-L482

👾 note: Please know I'm less confident about this change so consider it a non-blocking preference!

"""Column settings for TableBlock columns."""

@property
def attributes(self) -> Set[str]:
return {"align", "is_wrapped"}

def __init__(
self,
*,
align: Optional[str] = None,
is_wrapped: Optional[bool] = None,
**others: dict,
):
"""Settings for a single column in a table block.
https://docs.slack.dev/reference/block-kit/blocks/table-block

Args:
align: The alignment for items in this column. Can be "left", "center", or "right".
Defaults to "left" if not defined.
is_wrapped: Whether the contents of this column should be wrapped or not.
Defaults to false if not defined.
"""
show_unknown_key_warning(self, others)
self.align = align
self.is_wrapped = is_wrapped

@classmethod
def parse(cls, settings: Optional[Union[Dict[str, Any], "ColumnSettings"]]) -> Optional["ColumnSettings"]:
if settings is None:
return None
if isinstance(settings, ColumnSettings):
return settings
if isinstance(settings, dict):
return ColumnSettings(**settings)
return None


class RichTextCell(JsonObject):
"""A rich_text typed cell for use in TableBlock rows."""

type = "rich_text"

@property
def attributes(self) -> Set[str]:
return {"type", "elements"}

def __init__(
self,
*,
elements: Sequence[Union[Dict[str, Any], Any]],
**others: dict,
):
"""A rich text cell used in table block rows.
https://docs.slack.dev/reference/block-kit/blocks/table-block

Args:
elements (required): An array of rich text element objects
(rich_text_section, rich_text_list, rich_text_quote, rich_text_preformatted).
"""
show_unknown_key_warning(self, others)
self.type = self.__class__.type
from slack_sdk.models.blocks.block_elements import BlockElement

self.elements = BlockElement.parse_all(elements)

@classmethod
def parse(cls, cell: Optional[Union[Dict[str, Any], "RichTextCell"]]) -> Optional["RichTextCell"]:
if cell is None:
return None
if isinstance(cell, RichTextCell):
return cell
if isinstance(cell, dict):
d = {k: v for k, v in cell.items() if k != "type"}
return RichTextCell(**d)
return None


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class RichTextCell(JsonObject):
"""A rich_text typed cell for use in TableBlock rows."""
type = "rich_text"
@property
def attributes(self) -> Set[str]:
return {"type", "elements"}
def __init__(
self,
*,
elements: Sequence[Union[Dict[str, Any], Any]],
**others: dict,
):
"""A rich text cell used in table block rows.
https://docs.slack.dev/reference/block-kit/blocks/table-block
Args:
elements (required): An array of rich text element objects
(rich_text_section, rich_text_list, rich_text_quote, rich_text_preformatted).
"""
show_unknown_key_warning(self, others)
self.type = self.__class__.type
from slack_sdk.models.blocks.block_elements import BlockElement
self.elements = BlockElement.parse_all(elements)
@classmethod
def parse(cls, cell: Optional[Union[Dict[str, Any], "RichTextCell"]]) -> Optional["RichTextCell"]:
if cell is None:
return None
if isinstance(cell, RichTextCell):
return cell
if isinstance(cell, dict):
d = {k: v for k, v in cell.items() if k != "type"}
return RichTextCell(**d)
return None

🪓 note: I think here we can use the existing RichTextBlock similar to @slack/types implementation:

https://github.com/slackapi/node-slack-sdk/blob/122865134ffa20ad080fcf447cfb93b7252da157/packages/types/src/block-kit/blocks.ts#L450-L467

class Option(JsonObject):
"""Option object used in dialogs, legacy message actions (interactivity in attachments),
and blocks. JSON must be retrieved with an explicit option_type - the Slack API has
Expand Down
14 changes: 11 additions & 3 deletions slack_sdk/models/blocks/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
from slack_sdk.models.basic_objects import JsonObject, JsonValidator

from ...errors import SlackObjectFormationError
from .basic_components import MarkdownTextObject, PlainTextObject, SlackFile, TextObject
from .basic_components import (
ColumnSettings,
MarkdownTextObject,
PlainTextObject,
RawTextObject,
RichTextCell,
SlackFile,
TextObject,
)
from .block_elements import (
BlockElement,
FeedbackButtonsElement,
Expand Down Expand Up @@ -756,8 +764,8 @@ def attributes(self) -> Set[str]: # type: ignore[override]
def __init__(
self,
*,
rows: Sequence[Sequence[Dict[str, Any]]],
column_settings: Optional[Sequence[Optional[Dict[str, Any]]]] = None,
rows: Sequence[Sequence[Union[Dict[str, Any], "RawTextObject", "RichTextCell"]]],
column_settings: Optional[Sequence[Optional[Union[Dict[str, Any], "ColumnSettings"]]]] = None,
block_id: Optional[str] = None,
**others: dict,
):
Expand Down
65 changes: 65 additions & 0 deletions tests/slack_sdk/models/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CallBlock,
CardBlock,
CarouselBlock,
ColumnSettings,
ContextActionsBlock,
ContextBlock,
DividerBlock,
Expand All @@ -27,6 +28,7 @@
PlanBlock,
RawTextObject,
RichTextBlock,
RichTextCell,
RichTextElementParts,
RichTextListElement,
RichTextPreformattedElement,
Expand Down Expand Up @@ -1462,6 +1464,69 @@ def test_with_block_id(self):
}
self.assertDictEqual(input, TableBlock(**input).to_dict())

def test_with_column_settings_objects(self):
"""Test table using typed ColumnSettings objects"""
block = TableBlock(
rows=[[{"type": "raw_text", "text": "A"}, {"type": "raw_text", "text": "B"}]],
column_settings=[ColumnSettings(align="right", is_wrapped=True), ColumnSettings(align="left")],
)
expected = {
"type": "table",
"column_settings": [{"align": "right", "is_wrapped": True}, {"align": "left"}],
"rows": [[{"type": "raw_text", "text": "A"}, {"type": "raw_text", "text": "B"}]],
}
self.assertDictEqual(expected, block.to_dict())

def test_with_rich_text_cell_objects(self):
"""Test table using typed RichTextCell objects"""
cell = RichTextCell(elements=[{"type": "rich_text_section", "elements": [{"type": "text", "text": "Hello"}]}])
block = TableBlock(
rows=[
[RawTextObject(text="Header"), cell],
],
)
expected = {
"type": "table",
"rows": [
[
{"type": "raw_text", "text": "Header"},
{
"type": "rich_text",
"elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": "Hello"}]}],
},
]
],
}
self.assertDictEqual(expected, block.to_dict())

def test_mixed_typed_and_dict_cells(self):
"""Test table accepts a mix of typed objects and plain dicts"""
block = TableBlock(
rows=[
[RawTextObject(text="Col A"), RawTextObject(text="Col B")],
[
{"type": "raw_text", "text": "Data"},
RichTextCell(elements=[{"type": "rich_text_section", "elements": [{"type": "text", "text": "rich"}]}]),
],
],
column_settings=[ColumnSettings(align="left"), {"align": "right"}],
)
expected = {
"type": "table",
"column_settings": [{"align": "left"}, {"align": "right"}],
"rows": [
[{"type": "raw_text", "text": "Col A"}, {"type": "raw_text", "text": "Col B"}],
[
{"type": "raw_text", "text": "Data"},
{
"type": "rich_text",
"elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": "rich"}]}],
},
],
],
}
self.assertDictEqual(expected, block.to_dict())

def test_column_settings_variations(self):
"""Test various column_settings configurations"""
# Left align
Expand Down