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 @@ -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


Comment on lines +232 to +271

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