diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index db11558ab..6fa9f63cd 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -16,6 +16,7 @@ Option, OptionGroup, PlainTextObject, + RawNumberObject, RawTextObject, TableBlockColumnSettings, TextObject, @@ -93,6 +94,7 @@ "Option", "OptionGroup", "PlainTextObject", + "RawNumberObject", "RawTextObject", "TableBlockColumnSettings", "TextObject", diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py index c86007bab..451c8bb97 100644 --- a/slack_sdk/models/blocks/basic_components.py +++ b/slack_sdk/models/blocks/basic_components.py @@ -151,6 +151,28 @@ def direct_from_link(link: Link, title: str = "") -> Dict[str, Any]: return MarkdownTextObject.from_link(link, title).to_dict() +class RawNumberObject(JsonObject): + """raw_number typed object.""" + + type = "raw_number" + attributes = {"value", "text", "type"} + logger = logging.getLogger(__name__) + + def __init__(self, *, value: Union[int, float], text: str): + """Defines an object containing a numeric value. + + Args: + value (required): The numeric value. + text (required): The text used to display the value. The minimum length is 1 character. + """ + self.value = value + self.text = text + + @JsonValidator("text attribute must have at least 1 character") + def _validate_text_min_length(self): + return len(self.text) >= 1 + + class RawTextObject(TextObject): """raw_text typed text object.""" diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index 04b224159..bc9643571 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -10,6 +10,7 @@ from .basic_components import ( MarkdownTextObject, PlainTextObject, + RawNumberObject, RawTextObject, SlackFile, TableBlockColumnSettings, @@ -778,7 +779,7 @@ def attributes(self) -> Set[str]: # type: ignore[override] def __init__( self, *, - rows: Sequence[Sequence[Union[Dict[str, Any], "RawTextObject", "RichTextBlock"]]], + rows: Sequence[Sequence[Union[Dict[str, Any], "RawTextObject", "RawNumberObject", "RichTextBlock"]]], column_settings: Optional[Sequence[Optional[Union[Dict[str, Any], "TableBlockColumnSettings"]]]] = None, block_id: Optional[str] = None, **others: dict, @@ -790,7 +791,7 @@ def __init__( Args: rows (required): An array consisting of table rows. Maximum 100 rows. Each row object is an array with a max of 20 table cells. - Table cells can have a type of raw_text or rich_text. + Table cells can have a type of rich_text, raw_text, or raw_number. column_settings: An array describing column behavior. If there are fewer items in the column_settings array than there are columns in the table, then the items in the the column_settings array will describe the same number of columns in the table as there are in the array itself. diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index 369132c32..9c5de804f 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -26,6 +26,7 @@ OverflowMenuElement, PlainTextObject, PlanBlock, + RawNumberObject, RawTextObject, RichTextBlock, RichTextElementParts, @@ -1358,6 +1359,40 @@ def test_parsing_empty_block_elements(self): self.assertIsNotNone(block_dict["elements"][3].get("elements")) +# ---------------------------------------------- +# RawNumberObject +# ---------------------------------------------- + + +class RawNumberObjectTests(unittest.TestCase): + def test_basic_creation(self): + """Test basic RawNumberObject creation""" + obj = RawNumberObject(value=42, text="42") + expected = {"type": "raw_number", "value": 42, "text": "42"} + self.assertDictEqual(expected, obj.to_dict()) + + def test_float_value(self): + """Test RawNumberObject accepts a float value""" + obj = RawNumberObject(value=3.14, text="3.14") + expected = {"type": "raw_number", "value": 3.14, "text": "3.14"} + self.assertDictEqual(expected, obj.to_dict()) + + def test_text_length_validation_min(self): + """Test that empty text fails validation""" + with self.assertRaises(SlackObjectFormationError): + RawNumberObject(value=0, text="").to_dict() + + def test_text_length_validation_at_min(self): + """Test that text with 1 character passes validation""" + obj = RawNumberObject(value=1, text="1") + obj.to_dict() # Should not raise + + def test_attributes(self): + """Test that RawNumberObject only has value, text, and type attributes""" + obj = RawNumberObject(value=42, text="42") + self.assertEqual(obj.attributes, {"value", "text", "type"}) + + # ---------------------------------------------- # RawTextObject # ---------------------------------------------- @@ -1444,6 +1479,35 @@ def test_with_rich_text(self): self.assertDictEqual(input, TableBlock(**input).to_dict()) self.assertDictEqual(input, Block.parse(input).to_dict()) + def test_with_raw_number(self): + """Test table block with raw_number cells""" + input = { + "type": "table", + "rows": [ + [{"type": "raw_text", "text": "Widgets"}, {"type": "raw_number", "value": 42, "text": "42"}], + [ + { + "type": "rich_text", + "elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": "Gadgets"}]}], + }, + {"type": "raw_number", "value": 7, "text": "7"}, + ], + ], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + def test_with_raw_number_cell_objects(self): + """Test table using typed RawNumberObject cells""" + block = TableBlock( + rows=[[RawTextObject(text="Count"), RawNumberObject(value=42, text="42")]], + ) + expected = { + "type": "table", + "rows": [[{"type": "raw_text", "text": "Count"}, {"type": "raw_number", "value": 42, "text": "42"}]], + } + self.assertDictEqual(expected, block.to_dict()) + def test_minimal_table(self): """Test table with only required fields""" input = {