From cde6507dff238c74f7702caee40efab01c930098 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 4 Aug 2026 16:04:43 -0700 Subject: [PATCH 1/2] fix(models): CardBlock icon/hero_image accept an image element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the card block reference, `icon` and `hero_image` are image element objects (`type`/`image_url`/`alt_text`), not plain URL strings — but CardBlock typed them as Optional[str], so passing an ImageElement (the correct, accessible form with alt_text) tripped mypy [arg-type]. Widen both to Optional[Union[str, dict, ImageElement]], matching how title/subtitle/body already accept Union[str, dict, TextObject]. Runtime serialization is unchanged (str -> bare URL, dict/ImageElement -> object); this only broadens the accepted/typed input. Docstrings updated to the reference wording. Adds a test asserting the ImageElement form round-trips. Co-Authored-By: Claude --- slack_sdk/models/blocks/blocks.py | 10 ++++++---- tests/slack_sdk/models/test_blocks.py | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index db4de1f3a..5e7bb6f8c 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -949,8 +949,8 @@ def __init__( self, *, block_id: Optional[str] = None, - hero_image: Optional[str] = None, - icon: Optional[str] = None, + hero_image: Optional[Union[str, dict, ImageElement]] = None, + icon: Optional[Union[str, dict, ImageElement]] = None, title: Optional[Union[str, dict, TextObject]] = None, subtitle: Optional[Union[str, dict, TextObject]] = None, body: Optional[Union[str, dict, TextObject]] = None, @@ -962,8 +962,10 @@ def __init__( Args: block_id: A unique identifier for a block. If not specified, a block_id will be generated. - hero_image: Link to the top image used on the card. - icon: Link to the small image used next to the card's title and subtitle. + hero_image: Link to the top image used on the card. Max length 3000 characters. + The alt_text property has a max length of 2000 characters. + icon: Link to the small image used next to the card's title and subtitle. Max length + 3000 characters. The alt_text property has a max length of 2000 characters. title: Title of the card. 150 characters max. subtitle: Subtitle of the card. 150 characters max. body: Content of the card. 200 characters max. diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index fc9ff3266..449c6a83f 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -39,7 +39,7 @@ VideoBlock, ) from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile -from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement, ImageElement from . import STRING_3001_CHARS @@ -1575,6 +1575,24 @@ def test_document(self): } self.assertDictEqual(input, CardBlock(**input).to_dict()) + def test_image_element_icon_and_hero_image(self): + block = CardBlock( + icon=ImageElement(image_url="https://picsum.photos/36/36", alt_text="Icon"), + hero_image=ImageElement(image_url="https://picsum.photos/400/300", alt_text="Sample hero image"), + title=MarkdownTextObject(text="Lumon Industries"), + ) + expected = { + "type": "card", + "icon": {"type": "image", "image_url": "https://picsum.photos/36/36", "alt_text": "Icon"}, + "hero_image": { + "type": "image", + "image_url": "https://picsum.photos/400/300", + "alt_text": "Sample hero image", + }, + "title": {"type": "mrkdwn", "text": "Lumon Industries"}, + } + self.assertDictEqual(expected, block.to_dict()) + def test_parse(self): input = { "type": "card", From bb2a6a179564412ed87b4b30dbf2c92a82051787 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 4 Aug 2026 16:10:15 -0700 Subject: [PATCH 2/2] fix(models): type CardBlock icon/hero_image as image element, not str MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slack's card block schema (and the docs reference) define icon/hero_image as image element objects — a bare URL string is not a valid payload and is rejected by the API. CardBlock previously typed them as Optional[str], modeling a form that does not actually work end to end. Type both as Optional[Union[dict, ImageElement]] (object-only), matching title/subtitle/body's object-based typing. Update test_document to the documented object form (it previously passed bare-string icons). Runtime serialization of an ImageElement/dict was already correct; this drops the never-valid str form. Co-Authored-By: Claude --- slack_sdk/models/blocks/blocks.py | 4 ++-- tests/slack_sdk/models/test_blocks.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index 5e7bb6f8c..46ed040a2 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -949,8 +949,8 @@ def __init__( self, *, block_id: Optional[str] = None, - hero_image: Optional[Union[str, dict, ImageElement]] = None, - icon: Optional[Union[str, dict, ImageElement]] = None, + hero_image: Optional[Union[dict, ImageElement]] = None, + icon: Optional[Union[dict, ImageElement]] = None, title: Optional[Union[str, dict, TextObject]] = None, subtitle: Optional[Union[str, dict, TextObject]] = None, body: Optional[Union[str, dict, TextObject]] = None, diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index 449c6a83f..ba0d378b1 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -1560,10 +1560,14 @@ class CardBlockTests(unittest.TestCase): def test_document(self): input = { "type": "card", - "icon": "https://picsum.photos/36/36", + "icon": {"type": "image", "image_url": "https://picsum.photos/36/36", "alt_text": "Icon"}, "title": {"type": "mrkdwn", "text": "Lumon Industries", "verbatim": False}, "subtitle": {"type": "mrkdwn", "text": "Committed to work-life balance", "verbatim": False}, - "hero_image": "https://picsum.photos/400/300", + "hero_image": { + "type": "image", + "image_url": "https://picsum.photos/400/300", + "alt_text": "Sample hero image", + }, "body": {"type": "mrkdwn", "text": "Please enjoy each card equally.", "verbatim": False}, "actions": [ {