From beee68bc7f5d0297bfe4225c9e3f9b3037a30f2b Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Fri, 26 Jan 2024 15:04:27 +0900 Subject: [PATCH 1/3] Fix #1450 slack_file in image block/element --- slack_sdk/models/blocks/basic_components.py | 22 ++++++++++ slack_sdk/models/blocks/block_elements.py | 13 +++--- slack_sdk/models/blocks/blocks.py | 18 +++++--- tests/slack_sdk/models/test_blocks.py | 47 ++++++++++++++++++--- tests/slack_sdk/models/test_elements.py | 45 +++++++++++++++++--- 5 files changed, 123 insertions(+), 22 deletions(-) diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py index 3999b911..6cc5ba4d 100644 --- a/slack_sdk/models/blocks/basic_components.py +++ b/slack_sdk/models/blocks/basic_components.py @@ -559,3 +559,25 @@ def to_dict(self) -> Dict[str, Any]: # skipcq: PYL-W0221 else: json["trigger"] = self._trigger return json + + +class SlackFile(JsonObject): + attributes = {"id", "url"} + + def __init__( + self, + *, + id: Optional[str] = None, + url: Optional[str] = None, + ): + self._id = id + self._url = url + + def to_dict(self) -> Dict[str, Any]: # skipcq: PYL-W0221 + self.validate_json() + json = {} + if self._id is not None: + json["id"] = self._id + if self._url is not None: + json["url"] = self._url + return json diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py index 52af63b2..8977001e 100644 --- a/slack_sdk/models/blocks/block_elements.py +++ b/slack_sdk/models/blocks/block_elements.py @@ -11,7 +11,7 @@ JsonValidator, EnumValidator, ) -from .basic_components import ButtonStyles, Workflow +from .basic_components import ButtonStyles, Workflow, SlackFile from .basic_components import ConfirmObject from .basic_components import DispatchActionConfig from .basic_components import MarkdownTextObject @@ -551,13 +551,14 @@ class ImageElement(BlockElement): @property def attributes(self) -> Set[str]: - return super().attributes.union({"alt_text", "image_url"}) + return super().attributes.union({"alt_text", "image_url", "slack_file"}) def __init__( self, *, - image_url: Optional[str] = None, alt_text: Optional[str] = None, + image_url: Optional[str] = None, + slack_file: Optional[Union[Dict[str, Any], SlackFile]] = None, **others: dict, ): """An element to insert an image - this element can be used in section and @@ -566,18 +567,20 @@ def __init__( https://api.slack.com/reference/block-kit/block-elements#image Args: - image_url (required): The URL of the image to be displayed. alt_text (required): A plain-text summary of the image. This should not contain any markup. + image_url: The URL of the image to be displayed. + slack_file: A Slack image file object that defines the source of the image. """ super().__init__(type=self.type) show_unknown_key_warning(self, others) self.image_url = image_url self.alt_text = alt_text + self.slack_file = slack_file if slack_file is None or isinstance(slack_file, SlackFile) else SlackFile(**slack_file) @JsonValidator(f"image_url attribute cannot exceed {image_url_max_length} characters") def _validate_image_url_length(self) -> bool: - return len(self.image_url) <= self.image_url_max_length + return self.image_url is None or len(self.image_url) <= self.image_url_max_length @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters") def _validate_alt_text_length(self) -> bool: diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index 5d672131..2e9fa17d 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -8,7 +8,7 @@ JsonObject, JsonValidator, ) -from .basic_components import MarkdownTextObject +from .basic_components import MarkdownTextObject, SlackFile from .basic_components import PlainTextObject from .basic_components import TextObject from .block_elements import BlockElement, RichTextElement @@ -208,7 +208,7 @@ class ImageBlock(Block): @property def attributes(self) -> Set[str]: - return super().attributes.union({"alt_text", "image_url", "title"}) + return super().attributes.union({"alt_text", "image_url", "title", "slack_file"}) image_url_max_length = 3000 alt_text_max_length = 2000 @@ -217,8 +217,9 @@ def attributes(self) -> Set[str]: def __init__( self, *, - image_url: str, alt_text: str, + image_url: Optional[str] = None, + slack_file: Optional[Union[Dict[str, Any], SlackFile]] = None, title: Optional[Union[str, dict, PlainTextObject]] = None, block_id: Optional[str] = None, **others: dict, @@ -227,10 +228,11 @@ def __init__( https://api.slack.com/reference/block-kit/blocks#image Args: - image_url (required): The URL of the image to be displayed. - Maximum length for this field is 3000 characters. alt_text (required): A plain-text summary of the image. This should not contain any markup. Maximum length for this field is 2000 characters. + image_url: The URL of the image to be displayed. + Maximum length for this field is 3000 characters. + slack_file: A Slack image file object that defines the source of the image. title: An optional title for the image in the form of a text object that can only be of type: plain_text. Maximum length for the text in this field is 2000 characters. block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. @@ -255,11 +257,15 @@ def __init__( parsed_title = title else: raise SlackObjectFormationError(f"Unsupported type for title in an image block: {type(title)}") + if slack_file is not None: + self.slack_file = ( + slack_file if slack_file is None or isinstance(slack_file, SlackFile) else SlackFile(**slack_file) + ) self.title = parsed_title @JsonValidator(f"image_url attribute cannot exceed {image_url_max_length} characters") def _validate_image_url_length(self): - return len(self.image_url) <= self.image_url_max_length + return self.image_url is None or len(self.image_url) <= self.image_url_max_length @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters") def _validate_alt_text_length(self): diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index 836f65b1..b0de7d6e 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -29,6 +29,7 @@ RichTextPreformattedElement, RichTextElementParts, ) +from slack_sdk.models.blocks.basic_components import SlackFile from . import STRING_3001_CHARS @@ -151,7 +152,7 @@ def test_json(self): SectionBlock(text="some text", fields=[f"field{i}" for i in range(5)]).to_dict(), ) - button = LinkButtonElement(text="Click me!", url="http://google.com") + button = LinkButtonElement(text="Click me!", url="https://example.com") self.assertDictEqual( { "type": "section", @@ -314,11 +315,11 @@ def test_issue_1369_title_type(self): def test_json(self): self.assertDictEqual( { - "image_url": "http://google.com", + "image_url": "https://example.com", "alt_text": "not really an image", "type": "image", }, - ImageBlock(image_url="http://google.com", alt_text="not really an image").to_dict(), + ImageBlock(image_url="https://example.com", alt_text="not really an image").to_dict(), ) def test_image_url_length(self): @@ -327,13 +328,47 @@ def test_image_url_length(self): def test_alt_text_length(self): with self.assertRaises(SlackObjectFormationError): - ImageBlock(image_url="http://google.com", alt_text=STRING_3001_CHARS).to_dict() + ImageBlock(image_url="https://example.com", alt_text=STRING_3001_CHARS).to_dict() def test_title_length(self): with self.assertRaises(SlackObjectFormationError): - ImageBlock(image_url="http://google.com", alt_text="text", title=STRING_3001_CHARS).to_dict() + ImageBlock(image_url="https://example.com", alt_text="text", title=STRING_3001_CHARS).to_dict() + def test_slack_file(self): + self.assertDictEqual( + { + "slack_file": {"url": "https://example.com" }, + "alt_text": "not really an image", + "type": "image", + }, + ImageBlock(slack_file=SlackFile(url="https://example.com"), alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"id": "F11111" }, + "alt_text": "not really an image", + "type": "image", + }, + ImageBlock(slack_file=SlackFile(id="F11111"), alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"url": "https://example.com" }, + "alt_text": "not really an image", + "type": "image", + }, + ImageBlock(slack_file= {"url": "https://example.com" }, alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"id": "F11111" }, + "alt_text": "not really an image", + "type": "image", + }, + ImageBlock(slack_file={"id": "F11111" }, alt_text="not really an image").to_dict(), + ) + # ---------------------------------------------- # Actions # ---------------------------------------------- @@ -446,7 +481,7 @@ def test_document_2(self): def test_json(self): self.elements = [ ButtonElement(text="Click me", action_id="reg_button", value="1"), - LinkButtonElement(text="URL Button", url="http://google.com"), + LinkButtonElement(text="URL Button", url="https://example.com"), ] self.dict_elements = [] for e in self.elements: diff --git a/tests/slack_sdk/models/test_elements.py b/tests/slack_sdk/models/test_elements.py index 5a38e148..6f1b7a9a 100644 --- a/tests/slack_sdk/models/test_elements.py +++ b/tests/slack_sdk/models/test_elements.py @@ -28,6 +28,7 @@ InteractiveElement, PlainTextObject, ) +from slack_sdk.models.blocks.basic_components import SlackFile from slack_sdk.models.blocks.block_elements import ( DateTimePickerElement, EmailInputElement, @@ -186,11 +187,11 @@ def test_action_id(self): class LinkButtonElementTests(unittest.TestCase): def test_json(self): - button = LinkButtonElement(action_id="test", text="button text", url="http://google.com") + button = LinkButtonElement(action_id="test", text="button text", url="https://example.com") self.assertDictEqual( { "text": {"emoji": True, "text": "button text", "type": "plain_text"}, - "url": "http://google.com", + "url": "https://example.com", "type": "button", "action_id": button.action_id, }, @@ -456,11 +457,11 @@ def test_document(self): def test_json(self): self.assertDictEqual( { - "image_url": "http://google.com", + "image_url": "https://example.com", "alt_text": "not really an image", "type": "image", }, - ImageElement(image_url="http://google.com", alt_text="not really an image").to_dict(), + ImageElement(image_url="https://example.com", alt_text="not really an image").to_dict(), ) def test_image_url_length(self): @@ -469,7 +470,41 @@ def test_image_url_length(self): def test_alt_text_length(self): with self.assertRaises(SlackObjectFormationError): - ImageElement(image_url="http://google.com", alt_text=STRING_3001_CHARS).to_dict() + ImageElement(image_url="https://example.com", alt_text=STRING_3001_CHARS).to_dict() + + def test_slack_file(self): + self.assertDictEqual( + { + "slack_file": {"id": "F11111"}, + "alt_text": "not really an image", + "type": "image", + }, + ImageElement(slack_file=SlackFile(id="F11111"), alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"url": "https://example.com"}, + "alt_text": "not really an image", + "type": "image", + }, + ImageElement(slack_file=SlackFile(url="https://example.com"), alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"id": "F11111"}, + "alt_text": "not really an image", + "type": "image", + }, + ImageElement(slack_file={"id": "F11111"}, alt_text="not really an image").to_dict(), + ) + self.assertDictEqual( + { + "slack_file": {"url": "https://example.com"}, + "alt_text": "not really an image", + "type": "image", + }, + ImageElement(slack_file= {"url": "https://example.com"}, alt_text="not really an image").to_dict(), + ) # ------------------------------------------------- From 1dd159cbe73386fd3ef7a59a849a6e4a24abc589 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Fri, 26 Jan 2024 15:09:07 +0900 Subject: [PATCH 2/3] Format tests --- tests/slack_sdk/models/test_blocks.py | 14 +++++++------- tests/slack_sdk/models/test_elements.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index b0de7d6e..92abc761 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -334,11 +334,10 @@ def test_title_length(self): with self.assertRaises(SlackObjectFormationError): ImageBlock(image_url="https://example.com", alt_text="text", title=STRING_3001_CHARS).to_dict() - def test_slack_file(self): self.assertDictEqual( { - "slack_file": {"url": "https://example.com" }, + "slack_file": {"url": "https://example.com"}, "alt_text": "not really an image", "type": "image", }, @@ -346,7 +345,7 @@ def test_slack_file(self): ) self.assertDictEqual( { - "slack_file": {"id": "F11111" }, + "slack_file": {"id": "F11111"}, "alt_text": "not really an image", "type": "image", }, @@ -354,21 +353,22 @@ def test_slack_file(self): ) self.assertDictEqual( { - "slack_file": {"url": "https://example.com" }, + "slack_file": {"url": "https://example.com"}, "alt_text": "not really an image", "type": "image", }, - ImageBlock(slack_file= {"url": "https://example.com" }, alt_text="not really an image").to_dict(), + ImageBlock(slack_file={"url": "https://example.com"}, alt_text="not really an image").to_dict(), ) self.assertDictEqual( { - "slack_file": {"id": "F11111" }, + "slack_file": {"id": "F11111"}, "alt_text": "not really an image", "type": "image", }, - ImageBlock(slack_file={"id": "F11111" }, alt_text="not really an image").to_dict(), + ImageBlock(slack_file={"id": "F11111"}, alt_text="not really an image").to_dict(), ) + # ---------------------------------------------- # Actions # ---------------------------------------------- diff --git a/tests/slack_sdk/models/test_elements.py b/tests/slack_sdk/models/test_elements.py index 6f1b7a9a..8fa7bd01 100644 --- a/tests/slack_sdk/models/test_elements.py +++ b/tests/slack_sdk/models/test_elements.py @@ -503,7 +503,7 @@ def test_slack_file(self): "alt_text": "not really an image", "type": "image", }, - ImageElement(slack_file= {"url": "https://example.com"}, alt_text="not really an image").to_dict(), + ImageElement(slack_file={"url": "https://example.com"}, alt_text="not really an image").to_dict(), ) From 42d2bc0facfdc8fdd6908ef9a4e99d5eb0fb4ea5 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Mon, 29 Jan 2024 14:37:43 +0900 Subject: [PATCH 3/3] Update slack_sdk/models/blocks/basic_components.py --- slack_sdk/models/blocks/basic_components.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py index 6cc5ba4d..dc23736b 100644 --- a/slack_sdk/models/blocks/basic_components.py +++ b/slack_sdk/models/blocks/basic_components.py @@ -570,6 +570,13 @@ def __init__( id: Optional[str] = None, url: Optional[str] = None, ): + """An object containing Slack file information to be used in an image block or image element. + https://api.slack.com/reference/block-kit/composition-objects#slack_file + + Args: + id: Slack ID of the file. + url: This URL can be the url_private or the permalink of the Slack file. + """ self._id = id self._url = url