From 21ac4f79e8e8ab6fb868682fc3814522ea37ab6a Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 13 Nov 2025 11:41:16 -0800 Subject: [PATCH 01/30] chore: Add .github/CODEOWNERS file (#1791) --- .github/CODEOWNERS | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..4a08579c2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# Salesforce Open Source project configuration +# Learn more: https://github.com/salesforce/oss-template +#ECCN:Open Source +#GUSINFO:Open Source,Open Source Workflow + +# @slackapi/slack-platform-python +# are code reviewers for all changes in this repo. +* @slackapi/slack-platform-python + +# @slackapi/developer-education +# are code reviewers for changes in the `/docs` directory. +/docs/ @slackapi/developer-education From 998ee043209ec90adaa29f12c16210f218bfbb61 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 14 Nov 2025 09:48:24 -0500 Subject: [PATCH 02/30] fix: improve the release instructions (#1792) --- .github/maintainers_guide.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 611c2e97f..ccda1607f 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -175,15 +175,17 @@ Before creating a new release, ensure that everything on a stable branch has landed, then [run the tests](#unit-tests). 1. Create the commit for the release - 1. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and [Developmental Release](https://peps.python.org/pep-0440/#developmental-releases). + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and [Developmental Release](https://peps.python.org/pep-0440/#developmental-releases). - Example: if the current version is `1.2.3`, a proper development bump would be `1.2.4.dev0` - `.dev` will indicate to pip that this is a [Development Release](https://peps.python.org/pep-0440/#developmental-releases) - Note that the `dev` version can be bumped in development releases: `1.2.4.dev0` -> `1.2.4.dev1` - 2. Build the docs with `./scripts/generate_api_docs.sh`. - 3. Commit with a message including the new version number. For example `1.2.4.dev0` & push the commit to a branch where the development release will live (create it if it does not exist) + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.4.dev0` & push the commit to a branch where the development release will live (create it if it does not exist) 1. `git checkout -b future-release` - 2. `git commit -m 'chore(release): version 1.2.4.dev0'` - 3. `git push -u origin future-release` + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.4.dev0'` + 4. `git push -u origin future-release` 2. Create a new GitHub Release 1. Navigate to the [Releases page](https://github.com/slackapi/python-slack-sdk/releases). 2. Click the "Draft a new release" button. @@ -207,14 +209,16 @@ Before creating a new release, ensure that everything on the `main` branch since the last tag is in a releasable state! At a minimum, [run the tests](#unit-tests). 1. Create the commit for the release - 1. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and the [Versioning](#versioning-and-tags) section. - 2. Build the docs with `./scripts/generate_api_docs.sh`. - 3. Commit with a message including the new version number. For example `1.2.3` & push the commit to a branch and create a PR to sanity check. + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_sdk/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and the [Versioning](#versioning-and-tags) section. + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.3` & push the commit to a branch and create a PR to sanity check. 1. `git checkout -b 1.2.3-release` - 2. `git commit -m 'chore(release): version 1.2.3'` - 3. `git push -u origin 1.2.3-release` - 4. Add relevant labels to the PR and add the PR to a GitHub Milestone. - 5. Merge in release PR after getting an approval from at least one maintainer. + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.3'` + 4. `git push -u origin 1.2.3-release` + 5. Add relevant labels to the PR and add the PR to a GitHub Milestone. + 6. Merge in release PR after getting an approval from at least one maintainer. 2. Create a new GitHub Release 1. Navigate to the [Releases page](https://github.com/slackapi/python-slack-sdk/releases). 2. Click the "Draft a new release" button. From 4277ed87a0572cad92894b0b8b2b5104efe2d3f6 Mon Sep 17 00:00:00 2001 From: Elaine Vegeris Date: Mon, 17 Nov 2025 11:30:10 -0500 Subject: [PATCH 03/30] feat: Add work objects support (#1783) --- .../web/test_message_metadata.py | 119 +- slack_sdk/models/basic_objects.py | 9 +- slack_sdk/models/metadata/__init__.py | 1229 ++++++++++++++++- slack_sdk/web/async_client.py | 29 +- slack_sdk/web/client.py | 29 +- slack_sdk/web/internal_utils.py | 10 +- slack_sdk/web/legacy_client.py | 29 +- tests/slack_sdk/models/test_metadata.py | 307 ++++ 8 files changed, 1747 insertions(+), 14 deletions(-) create mode 100644 tests/slack_sdk/models/test_metadata.py diff --git a/integration_tests/web/test_message_metadata.py b/integration_tests/web/test_message_metadata.py index bb3a4de7b..dc2626d27 100644 --- a/integration_tests/web/test_message_metadata.py +++ b/integration_tests/web/test_message_metadata.py @@ -2,9 +2,32 @@ import os import time import unittest +import json from integration_tests.env_variable_names import SLACK_SDK_TEST_BOT_TOKEN -from slack_sdk.models.metadata import Metadata +from slack_sdk.models.metadata import ( + Metadata, + EventAndEntityMetadata, + EntityMetadata, + ExternalRef, + EntityPayload, + EntityAttributes, + EntityTitle, + TaskEntityFields, + EntityStringField, + EntityTitle, + EntityAttributes, + EntityFullSizePreview, + TaskEntityFields, + EntityTypedField, + EntityStringField, + EntityTimestampField, + EntityEditSupport, + EntityEditTextConfig, + EntityCustomField, + EntityUserIDField, + ExternalRef, +) from slack_sdk.web import WebClient @@ -125,3 +148,97 @@ def test_publishing_message_metadata_using_models(self): ) self.assertIsNone(scheduled.get("error")) self.assertIsNotNone(scheduled.get("message").get("metadata")) + + def test_publishing_entity_metadata(self): + client: WebClient = WebClient(token=self.bot_token) + new_message = client.chat_postMessage( + channel="C014KLZN9M0", + text="Message with entity metadata", + metadata={ + "entities": [ + { + "entity_type": "slack#/entities/task", + "url": "https://abc.com/123", + "external_ref": {"id": "123"}, + "entity_payload": { + "attributes": {"title": {"text": "My task"}, "product_name": "We reference only"}, + "fields": { + "due_date": {"value": "2026-06-06", "type": "slack#/types/date", "edit": {"enabled": True}}, + "created_by": {"type": "slack#/types/user", "user": {"user_id": "U014KLZE350"}}, + "date_created": {"value": 1760629278}, + }, + "custom_fields": [ + { + "label": "img", + "key": "img", + "type": "slack#/types/image", + "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/korel-1YjNtFtJlMTaC26A/o.jpg", + } + ], + }, + } + ] + }, + ) + + self.assertIsNone(new_message.get("error")) + self.assertIsNone(new_message.get("warning")) + + def test_publishing_entity_metadata_using_models(self): + # Build the metadata + + title = EntityTitle(text="My title") + full_size_preview = EntityFullSizePreview( + is_supported=True, + preview_url="https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg", + mime_type="image/jpeg", + ) + attributes = EntityAttributes(title=title, product_name="My Product", full_size_preview=full_size_preview) + description = EntityStringField( + value="Description of the task.", + long=True, + edit=EntityEditSupport(enabled=True, text=EntityEditTextConfig(min_length=5, max_length=100)), + ) + due_date = EntityTypedField(value="2026-06-06", type="slack#/types/date", edit=EntityEditSupport(enabled=True)) + created_by = EntityTypedField( + type="slack#/types/user", + user=EntityUserIDField(user_id="USLACKBOT"), + ) + date_created = EntityTimestampField(value=1762450663, type="slack#/types/timestamp") + date_updated = EntityTimestampField(value=1762450663, type="slack#/types/timestamp") + fields = TaskEntityFields( + description=description, + due_date=due_date, + created_by=created_by, + date_created=date_created, + date_updated=date_updated, + ) + custom_fields = [] + custom_fields.append( + EntityCustomField( + label="My Image", + key="my-image", + type="slack#/types/image", + image_url="https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg", + ) + ) + entity = EntityPayload(attributes=attributes, fields=fields, custom_fields=custom_fields) + + client: WebClient = WebClient(token=self.bot_token) + new_message = client.chat_postMessage( + channel="#random", + text="Message with entity metadata", + metadata=EventAndEntityMetadata( + entities=[ + EntityMetadata( + entity_type="slack#/entities/task", + external_ref=ExternalRef(id="abc123"), + url="https://myappdomain.com", + entity_payload=entity, + ) + ] + ), + ) + + self.assertIsNone(new_message.get("error")) + self.assertIsNone(new_message.get("warning")) diff --git a/slack_sdk/models/basic_objects.py b/slack_sdk/models/basic_objects.py index 339358790..4feefe3f6 100644 --- a/slack_sdk/models/basic_objects.py +++ b/slack_sdk/models/basic_objects.py @@ -40,6 +40,9 @@ def validate_json(self) -> None: if callable(method) and hasattr(method, "validator"): method() + def get_object_attribute(self, key: str): + return getattr(self, key, None) + def get_non_null_attributes(self) -> dict: """ Construct a dictionary out of non-null keys (from attributes property) @@ -57,7 +60,7 @@ def to_dict_compatible(value: Union[dict, list, object, tuple]) -> Union[dict, l return value def is_not_empty(self, key: str) -> bool: - value = getattr(self, key, None) + value = self.get_object_attribute(key) if value is None: return False @@ -75,7 +78,9 @@ def is_not_empty(self, key: str) -> bool: return value is not None return { - key: to_dict_compatible(getattr(self, key, None)) for key in sorted(self.attributes) if is_not_empty(self, key) + key: to_dict_compatible(value=self.get_object_attribute(key)) + for key in sorted(self.attributes) + if is_not_empty(self, key) } def to_dict(self, *args) -> dict: diff --git a/slack_sdk/models/metadata/__init__.py b/slack_sdk/models/metadata/__init__.py index ddf24f1ae..7e4918401 100644 --- a/slack_sdk/models/metadata/__init__.py +++ b/slack_sdk/models/metadata/__init__.py @@ -1,5 +1,5 @@ -from typing import Dict, Any -from slack_sdk.models.basic_objects import JsonObject +from typing import Dict, Any, Union, Optional, List +from slack_sdk.models.basic_objects import JsonObject, EnumValidator class Metadata(JsonObject): @@ -28,3 +28,1228 @@ def __str__(self): def __repr__(self): return self.__str__() + + +# +# Work object entity metadata +# https://docs.slack.dev/messaging/work-objects/ +# + + +"""Entity types""" +EntityType = { + "slack#/entities/task", + "slack#/entities/file", + "slack#/entities/item", + "slack#/entities/incident", + "slack#/entities/content_item", +} + + +"""Custom field types""" +CustomFieldType = { + "integer", + "string", + "array", + "boolean", + "slack#/types/date", + "slack#/types/timestamp", + "slack#/types/image", + "slack#/types/channel_id", + "slack#/types/user", + "slack#/types/entity_ref", + "slack#/types/link", + "slack#/types/email", +} + + +class ExternalRef(JsonObject): + """Reference (and optional type) used to identify an entity within the developer's system""" + + attributes = { + "id", + "type", + } + + def __init__( + self, + id: str, + type: Optional[str] = None, + **kwargs, + ): + self.id = id + self.type = type + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class FileEntitySlackFile(JsonObject): + """Slack file reference for file entities""" + + attributes = { + "id", + "type", + } + + def __init__( + self, + id: str, + type: Optional[str] = None, + **kwargs, + ): + self.id = id + self.type = type + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityIconSlackFile(JsonObject): + """Slack file reference for entity icon""" + + attributes = { + "id", + "url", + } + + def __init__( + self, + id: Optional[str] = None, + url: Optional[str] = None, + **kwargs, + ): + self.id = id + self.url = url + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityIconField(JsonObject): + """Icon field for entity attributes""" + + attributes = { + "alt_text", + "url", + "slack_file", + } + + def __init__( + self, + alt_text: str, + url: Optional[str] = None, + slack_file: Optional[Union[Dict[str, Any], EntityIconSlackFile]] = None, + **kwargs, + ): + self.alt_text = alt_text + self.url = url + self.slack_file = slack_file + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityEditSelectConfig(JsonObject): + """Select configuration for entity edit support""" + + attributes = { + "current_value", + "current_values", + "static_options", + "fetch_options_dynamically", + "min_query_length", + } + + def __init__( + self, + current_value: Optional[str] = None, + current_values: Optional[List[str]] = None, + static_options: Optional[List[Dict[str, Any]]] = None, # Option[] + fetch_options_dynamically: Optional[bool] = None, + min_query_length: Optional[int] = None, + **kwargs, + ): + self.current_value = current_value + self.current_values = current_values + self.static_options = static_options + self.fetch_options_dynamically = fetch_options_dynamically + self.min_query_length = min_query_length + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityEditNumberConfig(JsonObject): + """Number configuration for entity edit support""" + + attributes = { + "is_decimal_allowed", + "min_value", + "max_value", + } + + def __init__( + self, + is_decimal_allowed: Optional[bool] = None, + min_value: Optional[Union[int, float]] = None, + max_value: Optional[Union[int, float]] = None, + **kwargs, + ): + self.is_decimal_allowed = is_decimal_allowed + self.min_value = min_value + self.max_value = max_value + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityEditTextConfig(JsonObject): + """Text configuration for entity edit support""" + + attributes = { + "min_length", + "max_length", + } + + def __init__( + self, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + **kwargs, + ): + self.min_length = min_length + self.max_length = max_length + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityEditSupport(JsonObject): + """Edit support configuration for entity fields""" + + attributes = { + "enabled", + "placeholder", + "hint", + "optional", + "select", + "number", + "text", + } + + def __init__( + self, + enabled: bool, + placeholder: Optional[Dict[str, Any]] = None, # PlainTextElement + hint: Optional[Dict[str, Any]] = None, # PlainTextElement + optional: Optional[bool] = None, + select: Optional[Union[Dict[str, Any], EntityEditSelectConfig]] = None, + number: Optional[Union[Dict[str, Any], EntityEditNumberConfig]] = None, + text: Optional[Union[Dict[str, Any], EntityEditTextConfig]] = None, + **kwargs, + ): + self.enabled = enabled + self.placeholder = placeholder + self.hint = hint + self.optional = optional + self.select = select + self.number = number + self.text = text + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityFullSizePreviewError(JsonObject): + """Error information for full-size preview""" + + attributes = { + "code", + "message", + } + + def __init__( + self, + code: str, + message: Optional[str] = None, + **kwargs, + ): + self.code = code + self.message = message + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityFullSizePreview(JsonObject): + """Full-size preview configuration for entity""" + + attributes = { + "is_supported", + "preview_url", + "mime_type", + "error", + } + + def __init__( + self, + is_supported: bool, + preview_url: Optional[str] = None, + mime_type: Optional[str] = None, + error: Optional[Union[Dict[str, Any], EntityFullSizePreviewError]] = None, + **kwargs, + ): + self.is_supported = is_supported + self.preview_url = preview_url + self.mime_type = mime_type + self.error = error + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityUserIDField(JsonObject): + """User ID field for entity""" + + attributes = { + "user_id", + } + + def __init__( + self, + user_id: str, + **kwargs, + ): + self.user_id = user_id + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityUserField(JsonObject): + """User field for entity""" + + attributes = { + "text", + "url", + "email", + "icon", + } + + def __init__( + self, + text: str, + url: Optional[str] = None, + email: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + **kwargs, + ): + self.text = text + self.url = url + self.email = email + self.icon = icon + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityRefField(JsonObject): + """Entity reference field""" + + attributes = { + "entity_url", + "external_ref", + "title", + "display_type", + "icon", + } + + def __init__( + self, + entity_url: str, + external_ref: Union[Dict[str, Any], ExternalRef], + title: str, + display_type: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + **kwargs, + ): + self.entity_url = entity_url + self.external_ref = external_ref + self.title = title + self.display_type = display_type + self.icon = icon + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityTypedField(JsonObject): + """Typed field for entity with various display options""" + + attributes = { + "type", + "label", + "value", + "link", + "icon", + "long", + "format", + "image_url", + "slack_file", + "alt_text", + "edit", + "tag_color", + "user", + "entity_ref", + } + + def __init__( + self, + type: str, + label: Optional[str] = None, + value: Optional[Union[str, int]] = None, + link: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + long: Optional[bool] = None, + format: Optional[str] = None, + image_url: Optional[str] = None, + slack_file: Optional[Dict[str, Any]] = None, + alt_text: Optional[str] = None, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + tag_color: Optional[str] = None, + user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None, + entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None, + **kwargs, + ): + self.type = type + self.label = label + self.value = value + self.link = link + self.icon = icon + self.long = long + self.format = format + self.image_url = image_url + self.slack_file = slack_file + self.alt_text = alt_text + self.edit = edit + self.tag_color = tag_color + self.user = user + self.entity_ref = entity_ref + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityStringField(JsonObject): + """String field for entity""" + + attributes = { + "value", + "label", + "format", + "link", + "icon", + "long", + "type", + "tag_color", + "edit", + } + + def __init__( + self, + value: str, + label: Optional[str] = None, + format: Optional[str] = None, + link: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + long: Optional[bool] = None, + type: Optional[str] = None, + tag_color: Optional[str] = None, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + **kwargs, + ): + self.value = value + self.label = label + self.format = format + self.link = link + self.icon = icon + self.long = long + self.type = type + self.tag_color = tag_color + self.edit = edit + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityTimestampField(JsonObject): + """Timestamp field for entity""" + + attributes = { + "value", + "label", + "type", + "edit", + } + + def __init__( + self, + value: int, + label: Optional[str] = None, + type: Optional[str] = None, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + **kwargs, + ): + self.value = value + self.label = label + self.type = type + self.edit = edit + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityImageField(JsonObject): + """Image field for entity""" + + attributes = { + "alt_text", + "label", + "image_url", + "slack_file", + "title", + "type", + } + + def __init__( + self, + alt_text: str, + label: Optional[str] = None, + image_url: Optional[str] = None, + slack_file: Optional[Dict[str, Any]] = None, + title: Optional[str] = None, + type: Optional[str] = None, + **kwargs, + ): + self.alt_text = alt_text + self.label = label + self.image_url = image_url + self.slack_file = slack_file + self.title = title + self.type = type + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityBooleanCheckboxField(JsonObject): + """Boolean checkbox properties""" + + attributes = {"type", "text", "description"} + + def __init__( + self, + type: str, + text: str, + description: Optional[str], + **kwargs, + ): + self.type = type + self.text = text + self.description = description + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityBooleanTextField(JsonObject): + """Boolean text properties""" + + attributes = {"type", "true_text", "false_text", "true_description", "false_description"} + + def __init__( + self, + type: str, + true_text: str, + false_text: str, + true_description: Optional[str], + false_description: Optional[str], + **kwargs, + ): + self.type = type + self.true_text = (true_text,) + self.false_text = (false_text,) + self.true_description = (true_description,) + self.false_description = (false_description,) + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityArrayItemField(JsonObject): + """Array item field for entity (similar to EntityTypedField but with optional type)""" + + attributes = { + "type", + "label", + "value", + "link", + "icon", + "long", + "format", + "image_url", + "slack_file", + "alt_text", + "edit", + "tag_color", + "user", + "entity_ref", + } + + def __init__( + self, + type: Optional[str] = None, + label: Optional[str] = None, + value: Optional[Union[str, int]] = None, + link: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + long: Optional[bool] = None, + format: Optional[str] = None, + image_url: Optional[str] = None, + slack_file: Optional[Dict[str, Any]] = None, + alt_text: Optional[str] = None, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + tag_color: Optional[str] = None, + user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None, + entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None, + **kwargs, + ): + self.type = type + self.label = label + self.value = value + self.link = link + self.icon = icon + self.long = long + self.format = format + self.image_url = image_url + self.slack_file = slack_file + self.alt_text = alt_text + self.edit = edit + self.tag_color = tag_color + self.user = user + self.entity_ref = entity_ref + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityCustomField(JsonObject): + """Custom field for entity with flexible types""" + + attributes = { + "label", + "key", + "type", + "value", + "link", + "icon", + "long", + "format", + "image_url", + "slack_file", + "alt_text", + "tag_color", + "edit", + "item_type", + "user", + "entity_ref", + "boolean", + } + + def __init__( + self, + label: str, + key: str, + type: str, + value: Optional[Union[str, int, List[Union[Dict[str, Any], EntityArrayItemField]]]] = None, + link: Optional[str] = None, + icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + long: Optional[bool] = None, + format: Optional[str] = None, + image_url: Optional[str] = None, + slack_file: Optional[Dict[str, Any]] = None, + alt_text: Optional[str] = None, + tag_color: Optional[str] = None, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + item_type: Optional[str] = None, + user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None, + entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None, + boolean: Optional[Union[Dict[str, Any], EntityBooleanCheckboxField, EntityBooleanTextField]] = None, + **kwargs, + ): + self.label = label + self.key = key + self.type = type + self.value = value + self.link = link + self.icon = icon + self.long = long + self.format = format + self.image_url = image_url + self.slack_file = slack_file + self.alt_text = alt_text + self.tag_color = tag_color + self.edit = edit + self.item_type = item_type + self.user = user + self.entity_ref = entity_ref + self.boolean = boolean + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + @EnumValidator("type", CustomFieldType) + def type_valid(self): + return self.type is None or self.type in CustomFieldType + + +class FileEntityFields(JsonObject): + """Fields specific to file entities""" + + attributes = { + "preview", + "created_by", + "date_created", + "date_updated", + "last_modified_by", + "file_size", + "mime_type", + "full_size_preview", + } + + def __init__( + self, + preview: Optional[Union[Dict[str, Any], EntityImageField]] = None, + created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + file_size: Optional[Union[Dict[str, Any], EntityStringField]] = None, + mime_type: Optional[Union[Dict[str, Any], EntityStringField]] = None, + full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None, + **kwargs, + ): + self.preview = preview + self.created_by = created_by + self.date_created = date_created + self.date_updated = date_updated + self.last_modified_by = last_modified_by + self.file_size = file_size + self.mime_type = mime_type + self.full_size_preview = full_size_preview + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class TaskEntityFields(JsonObject): + """Fields specific to task entities""" + + attributes = { + "description", + "created_by", + "date_created", + "date_updated", + "assignee", + "status", + "due_date", + "priority", + } + + def __init__( + self, + description: Optional[Union[Dict[str, Any], EntityStringField]] = None, + created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + assignee: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + status: Optional[Union[Dict[str, Any], EntityStringField]] = None, + due_date: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + priority: Optional[Union[Dict[str, Any], EntityStringField]] = None, + **kwargs, + ): + self.description = description + self.created_by = created_by + self.date_created = date_created + self.date_updated = date_updated + self.assignee = assignee + self.status = status + self.due_date = due_date + self.priority = priority + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class IncidentEntityFields(JsonObject): + """Fields specific to incident entities""" + + attributes = { + "status", + "priority", + "urgency", + "created_by", + "assigned_to", + "date_created", + "date_updated", + "description", + "service", + } + + def __init__( + self, + status: Optional[Union[Dict[str, Any], EntityStringField]] = None, + priority: Optional[Union[Dict[str, Any], EntityStringField]] = None, + urgency: Optional[Union[Dict[str, Any], EntityStringField]] = None, + created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + assigned_to: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + description: Optional[Union[Dict[str, Any], EntityStringField]] = None, + service: Optional[Union[Dict[str, Any], EntityStringField]] = None, + **kwargs, + ): + self.status = status + self.priority = priority + self.urgency = urgency + self.created_by = created_by + self.assigned_to = assigned_to + self.date_created = date_created + self.date_updated = date_updated + self.description = description + self.service = service + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class ContentItemEntityFields(JsonObject): + """Fields specific to content item entities""" + + attributes = { + "preview", + "description", + "created_by", + "date_created", + "date_updated", + "last_modified_by", + } + + def __init__( + self, + preview: Optional[Union[Dict[str, Any], EntityImageField]] = None, + description: Optional[Union[Dict[str, Any], EntityStringField]] = None, + created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None, + last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None, + **kwargs, + ): + self.preview = preview + self.description = description + self.created_by = created_by + self.date_created = date_created + self.date_updated = date_updated + self.last_modified_by = last_modified_by + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityActionProcessingState(JsonObject): + """Processing state configuration for entity action button""" + + attributes = { + "enabled", + "interstitial_text", + } + + def __init__( + self, + enabled: bool, + interstitial_text: Optional[str] = None, + **kwargs, + ): + self.enabled = enabled + self.interstitial_text = interstitial_text + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityActionButton(JsonObject): + """Action button for entity""" + + attributes = { + "text", + "action_id", + "value", + "style", + "url", + "accessibility_label", + "processing_state", + } + + def __init__( + self, + text: str, + action_id: str, + value: Optional[str] = None, + style: Optional[str] = None, + url: Optional[str] = None, + accessibility_label: Optional[str] = None, + processing_state: Optional[Union[Dict[str, Any], EntityActionProcessingState]] = None, + **kwargs, + ): + self.text = text + self.action_id = action_id + self.value = value + self.style = style + self.url = url + self.accessibility_label = accessibility_label + self.processing_state = processing_state + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityTitle(JsonObject): + """Title for entity attributes""" + + attributes = { + "text", + "edit", + } + + def __init__( + self, + text: str, + edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None, + **kwargs, + ): + self.text = text + self.edit = edit + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityAttributes(JsonObject): + """Attributes for an entity""" + + attributes = { + "title", + "display_type", + "display_id", + "product_icon", + "product_name", + "locale", + "full_size_preview", + "metadata_last_modified", + } + + def __init__( + self, + title: Union[Dict[str, Any], EntityTitle], + display_type: Optional[str] = None, + display_id: Optional[str] = None, + product_icon: Optional[Union[Dict[str, Any], EntityIconField]] = None, + product_name: Optional[str] = None, + locale: Optional[str] = None, + full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None, + metadata_last_modified: Optional[int] = None, + **kwargs, + ): + self.title = title + self.display_type = display_type + self.display_id = display_id + self.product_icon = product_icon + self.product_name = product_name + self.locale = locale + self.full_size_preview = full_size_preview + self.metadata_last_modified = metadata_last_modified + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityActions(JsonObject): + """Actions configuration for entity""" + + attributes = { + "primary_actions", + "overflow_actions", + } + + def __init__( + self, + primary_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None, + overflow_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None, + **kwargs, + ): + self.primary_actions = primary_actions + self.overflow_actions = overflow_actions + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityPayload(JsonObject): + """Payload schema for an entity""" + + attributes = { + "attributes", + "fields", + "custom_fields", + "slack_file", + "display_order", + "actions", + } + + def __init__( + self, + attributes: Union[Dict[str, Any], EntityAttributes], + fields: Optional[ + Union[Dict[str, Any], ContentItemEntityFields, FileEntityFields, IncidentEntityFields, TaskEntityFields] + ] = None, + custom_fields: Optional[List[Union[Dict[str, Any], EntityCustomField]]] = None, + slack_file: Optional[Union[Dict[str, Any], FileEntitySlackFile]] = None, + display_order: Optional[List[str]] = None, + actions: Optional[Union[Dict[str, Any], EntityActions]] = None, + **kwargs, + ): + # Store entity attributes data with a different internal name to avoid + # shadowing the class-level 'attributes' set used for JSON serialization + self._entity_attributes = attributes + self.fields = fields + self.custom_fields = custom_fields + self.slack_file = slack_file + self.display_order = display_order + self.actions = actions + self.additional_attributes = kwargs + + @property + def entity_attributes(self) -> Union[Dict[str, Any], EntityAttributes]: + """Get the entity attributes data. + + Note: Use this property to access the attributes data. The class-level + 'attributes' is reserved for the JSON serialization schema. + """ + return self._entity_attributes + + @entity_attributes.setter + def entity_attributes(self, value: Union[Dict[str, Any], EntityAttributes]): + """Set the entity attributes data.""" + self._entity_attributes = value + + def get_object_attribute(self, key: str): + if key == "attributes": + return self._entity_attributes + else: + return getattr(self, key, None) + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + +class EntityMetadata(JsonObject): + """Work object entity metadata + + https://docs.slack.dev/messaging/work-objects/ + """ + + attributes = { + "entity_type", + "entity_payload", + "external_ref", + "url", + "app_unfurl_url", + } + + def __init__( + self, + entity_type: str, + entity_payload: Union[Dict[str, Any], EntityPayload], + external_ref: Union[Dict[str, Any], ExternalRef], + url: str, + app_unfurl_url: Optional[str] = None, + **kwargs, + ): + self.entity_type = entity_type + self.entity_payload = entity_payload + self.external_ref = external_ref + self.url = url + self.app_unfurl_url = app_unfurl_url + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() + + @EnumValidator("entity_type", EntityType) + def entity_type_valid(self): + return self.entity_type is None or self.entity_type in EntityType + + +class EventAndEntityMetadata(JsonObject): + """Message metadata with entities + + https://docs.slack.dev/messaging/message-metadata/ + https://docs.slack.dev/messaging/work-objects/ + """ + + attributes = {"event_type", "event_payload", "entities"} + + def __init__( + self, + event_type: Optional[str] = None, + event_payload: Optional[Dict[str, Any]] = None, + entities: Optional[List[Union[Dict[str, Any], EntityMetadata]]] = None, + **kwargs, + ): + self.event_type = event_type + self.event_payload = event_payload + self.entities = entities + self.additional_attributes = kwargs + + def __str__(self): + return str(self.get_non_null_attributes()) + + def __repr__(self): + return self.__str__() diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index 7b82a0923..050e93294 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -22,7 +22,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata +from ..models.metadata import Metadata, EventAndEntityMetadata from .async_base_client import AsyncBaseClient, AsyncSlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2769,7 +2769,7 @@ async def chat_postMessage( link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> AsyncSlackResponse: @@ -2998,6 +2998,7 @@ async def chat_unfurl( source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3014,6 +3015,7 @@ async def chat_unfurl( "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3645,6 +3647,29 @@ async def emoji_list( kwargs.update({"include_categories": include_categories}) return await self.api_call("emoji.list", http_verb="GET", params=kwargs) + async def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[dict] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, str]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + return await self.api_call("entity.presentDetails", json=kwargs) + async def files_comments_delete( self, *, diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 8410ffee2..830f481d0 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -12,7 +12,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata +from ..models.metadata import Metadata, EventAndEntityMetadata from .base_client import BaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2759,7 +2759,7 @@ def chat_postMessage( link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -2988,6 +2988,7 @@ def chat_unfurl( source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3004,6 +3005,7 @@ def chat_unfurl( "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3635,6 +3637,29 @@ def emoji_list( kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[dict] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, str]] = None, + **kwargs, + ) -> SlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, diff --git a/slack_sdk/web/internal_utils.py b/slack_sdk/web/internal_utils.py index fadda929f..57afc6be4 100644 --- a/slack_sdk/web/internal_utils.py +++ b/slack_sdk/web/internal_utils.py @@ -17,7 +17,7 @@ from slack_sdk.errors import SlackRequestError from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block -from slack_sdk.models.metadata import Metadata +from slack_sdk.models.metadata import Metadata, EventAndEntityMetadata, EntityMetadata def convert_bool_to_0_or_1(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: @@ -187,13 +187,17 @@ def _build_req_args( def _parse_web_class_objects(kwargs) -> None: - def to_dict(obj: Union[Dict, Block, Attachment, Metadata]): + def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata, EntityMetadata]): if isinstance(obj, Block): return obj.to_dict() if isinstance(obj, Attachment): return obj.to_dict() if isinstance(obj, Metadata): return obj.to_dict() + if isinstance(obj, EventAndEntityMetadata): + return obj.to_dict() + if isinstance(obj, EntityMetadata): + return obj.to_dict() return obj for blocks_name in ["blocks", "user_auth_blocks"]: @@ -208,7 +212,7 @@ def to_dict(obj: Union[Dict, Block, Attachment, Metadata]): kwargs.update({"attachments": dict_attachments}) metadata = kwargs.get("metadata", None) - if metadata is not None and isinstance(metadata, Metadata): + if metadata is not None and (isinstance(metadata, Metadata) or isinstance(metadata, EventAndEntityMetadata)): kwargs.update({"metadata": to_dict(metadata)}) diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index 88c0fcf1a..cce4bd1ad 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -23,7 +23,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata +from ..models.metadata import Metadata, EventAndEntityMetadata from .legacy_base_client import LegacyBaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2770,7 +2770,7 @@ def chat_postMessage( link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> Union[Future, SlackResponse]: @@ -2936,6 +2936,7 @@ def chat_unfurl( source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -2952,6 +2953,7 @@ def chat_unfurl( "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3583,6 +3585,29 @@ def emoji_list( kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[dict] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, str]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, diff --git a/tests/slack_sdk/models/test_metadata.py b/tests/slack_sdk/models/test_metadata.py new file mode 100644 index 000000000..14635c661 --- /dev/null +++ b/tests/slack_sdk/models/test_metadata.py @@ -0,0 +1,307 @@ +import unittest + +from slack_sdk.models.metadata import ( + EventAndEntityMetadata, + EntityMetadata, + ExternalRef, + FileEntitySlackFile, + EntityIconField, + EntityEditTextConfig, + EntityEditSupport, + EntityFullSizePreviewError, + EntityFullSizePreview, + EntityUserIDField, + EntityUserField, + EntityTypedField, + EntityStringField, + EntityTimestampField, + EntityImageField, + EntityCustomField, + FileEntityFields, + TaskEntityFields, + EntityActionButton, + EntityTitle, + EntityAttributes, + EntityActions, + EntityPayload, +) + + +class EntityMetadataTests(unittest.TestCase): + maxDiff = None + + # ============================================================================ + # Entity JSON + # ============================================================================ + + task_entity_json = { + "app_unfurl_url": "https://myappdomain.com/123?myquery=param", + "entity_type": "slack#/entities/task", + "url": "https://myappdomain.com/123", + "external_ref": {"id": "123"}, + "entity_payload": { + "attributes": { + "title": {"text": "My Title"}, + "display_type": "Incident", + "display_id": "123", + "product_name": "My Product", + }, + "fields": { + "date_created": {"value": 1741164235}, + "status": {"value": "In Progress"}, + "description": { + "value": "My Description", + "long": True, + "edit": {"enabled": True, "text": {"min_length": 5, "max_length": 100}}, + }, + "due_date": {"value": "2026-06-06", "type": "slack#/types/date"}, + "created_by": {"type": "slack#/types/user", "user": {"user_id": "USLACKBOT"}}, + }, + "custom_fields": [ + { + "label": "My Users", + "key": "my-users", + "type": "array", + "item_type": "slack#/types/user", + "value": [ + {"type": "slack#/types/user", "user": {"user_id": "USLACKBOT"}}, + { + "type": "slack#/types/user", + "user": { + "text": "John Smith", + "email": "j@example.com", + "icon": {"alt_text": "Avatar", "url": "https://my-hosted-icon.com"}, + }, + }, + ], + } + ], + }, + } + + file_entity_json = { + "app_unfurl_url": "https://myappdomain.com/file/456?view=preview", + "entity_type": "slack#/entities/file", + "url": "https://myappdomain.com/file/456", + "external_ref": {"id": "456", "type": "DOC"}, + "entity_payload": { + "attributes": { + "title": {"text": "Q4 Product Roadmap"}, + "display_type": "PDF Document", + "display_id": "DOC-456", + "product_icon": {"alt_text": "Product Logo", "url": "https://myappdomain.com/icons/logo.png"}, + "product_name": "FileVault Pro", + "locale": "en-US", + "full_size_preview": { + "is_supported": True, + "preview_url": "https://myappdomain.com/previews/456/full.png", + "mime_type": "image/png", + }, + }, + "fields": { + "preview": { + "alt_text": "Document preview thumbnail", + "label": "Preview", + "image_url": "https://myappdomain.com/previews/456/thumb.png", + "type": "slack#/types/image", + }, + "date_created": {"value": 1709554321, "type": "slack#/types/timestamp"}, + "mime_type": {"value": "application/pdf"}, + }, + "slack_file": {"id": "F123ABC456", "type": "pdf"}, + "display_order": ["date_created", "mime_type", "preview"], + "actions": { + "primary_actions": [ + { + "text": "Open", + "action_id": "open_file", + "value": "456", + "style": "primary", + "url": "https://myappdomain.com/file/456/view", + } + ], + "overflow_actions": [{"text": "Delete", "action_id": "delete_file", "value": "456", "style": "danger"}], + }, + }, + } + + # ============================================================================ + # Methods returning re-usable metadata components + # ============================================================================ + + def attributes(self): + return EntityAttributes( + title=EntityTitle(text="My Title"), + product_name="My Product", + display_type="Incident", + display_id="123", + ) + + def sample_file_attributes(self): + return EntityAttributes( + title=EntityTitle(text="Q4 Product Roadmap"), + display_type="PDF Document", + display_id="DOC-456", + product_icon=EntityIconField(alt_text="Product Logo", url="https://myappdomain.com/icons/logo.png"), + product_name="FileVault Pro", + locale="en-US", + full_size_preview=EntityFullSizePreview( + is_supported=True, preview_url="https://myappdomain.com/previews/456/full.png", mime_type="image/png" + ), + ) + + def user_array_custom_field(self): + return EntityCustomField( + label="My Users", + key="my-users", + type="array", + item_type="slack#/types/user", + value=[ + EntityTypedField(type="slack#/types/user", user=EntityUserIDField(user_id="USLACKBOT")), + EntityTypedField( + type="slack#/types/user", + user=EntityUserField( + text="John Smith", + email="j@example.com", + icon=EntityIconField(alt_text="Avatar", url="https://my-hosted-icon.com"), + ), + ), + ], + ) + + def task_fields(self): + return TaskEntityFields( + date_created=EntityTimestampField(value=1741164235), + status=EntityStringField(value="In Progress"), + description=EntityStringField( + value="My Description", + long=True, + edit=EntityEditSupport(enabled=True, text=EntityEditTextConfig(min_length=5, max_length=100)), + ), + due_date=EntityTypedField(value="2026-06-06", type="slack#/types/date"), + created_by=EntityTypedField( + type="slack#/types/user", + user=EntityUserIDField(user_id="USLACKBOT"), + ), + ) + + def file_fields(self): + return FileEntityFields( + preview=EntityImageField( + type="slack#/types/image", + alt_text="Document preview thumbnail", + label="Preview", + image_url="https://myappdomain.com/previews/456/thumb.png", + ), + date_created=EntityTimestampField(value=1709554321, type="slack#/types/timestamp"), + mime_type=EntityStringField(value="application/pdf"), + ) + + def supported_full_size_preview(self): + return EntityFullSizePreview( + is_supported=True, preview_url="https://example.com/preview.jpg", mime_type="image/jpeg" + ) + + def sample_file_actions(self): + return EntityActions( + primary_actions=[ + EntityActionButton( + text="Open", + action_id="open_file", + value="456", + style="primary", + url="https://myappdomain.com/file/456/view", + ) + ], + overflow_actions=[EntityActionButton(text="Delete", action_id="delete_file", value="456", style="danger")], + ) + + # ============================================================================ + # Tests + # ============================================================================ + + def test_entity_full_size_preview_error(self): + error = EntityFullSizePreviewError(code="not_found", message="File not found") + self.assertDictEqual(error.to_dict(), {"code": "not_found", "message": "File not found"}) + + def test_entity_full_size_preview_with_error(self): + preview = EntityFullSizePreview( + is_supported=False, error=EntityFullSizePreviewError(code="invalid_format", message="File not found") + ) + result = preview.to_dict() + self.assertFalse(result["is_supported"]) + self.assertIn("error", result) + + def test_attributes(self): + self.assertDictEqual( + self.attributes().to_dict(), + self.task_entity_json["entity_payload"]["attributes"], + ) + + def test_sample_file_attributes(self): + self.assertDictEqual( + self.sample_file_attributes().to_dict(), + self.file_entity_json["entity_payload"]["attributes"], + ) + + def test_array_custom_field(self): + self.assertDictEqual( + self.user_array_custom_field().to_dict(), + self.task_entity_json["entity_payload"]["custom_fields"][0], + ) + + def test_task_fields(self): + self.assertDictEqual( + self.task_fields().to_dict(), + self.task_entity_json["entity_payload"]["fields"], + ) + + def test_file_fields(self): + self.assertDictEqual( + self.file_fields().to_dict(), + self.file_entity_json["entity_payload"]["fields"], + ) + + def test_sample_file_actions(self): + self.assertDictEqual( + self.sample_file_actions().to_dict(), + self.file_entity_json["entity_payload"]["actions"], + ) + + def test_complete_task_entity_metadata(self): + entity_metadata = EventAndEntityMetadata( + entities=[ + EntityMetadata( + entity_type="slack#/entities/task", + external_ref=ExternalRef(id="123"), + url="https://myappdomain.com/123", + app_unfurl_url="https://myappdomain.com/123?myquery=param", + entity_payload=EntityPayload( + attributes=self.attributes(), + fields=self.task_fields(), + custom_fields=[self.user_array_custom_field()], + ), + ) + ] + ) + self.assertDictEqual(entity_metadata.to_dict(), {"entities": [self.task_entity_json]}) + + def test_complete_file_entity_metadata(self): + entity_metadata = EventAndEntityMetadata( + entities=[ + EntityMetadata( + entity_type="slack#/entities/file", + external_ref=ExternalRef(id="456", type="DOC"), + url="https://myappdomain.com/file/456", + app_unfurl_url="https://myappdomain.com/file/456?view=preview", + entity_payload=EntityPayload( + attributes=self.sample_file_attributes(), + fields=self.file_fields(), + slack_file=FileEntitySlackFile(id="F123ABC456", type="pdf"), + display_order=["date_created", "mime_type", "preview"], + actions=self.sample_file_actions(), + ), + ) + ] + ) + self.assertDictEqual(entity_metadata.to_dict(), {"entities": [self.file_entity_json]}) From e802f1716719f5ed3996f966e2c341033e1b0053 Mon Sep 17 00:00:00 2001 From: Elaine Vegeris Date: Tue, 18 Nov 2025 12:59:41 -0500 Subject: [PATCH 04/30] feat: update work objects support (entity.presentDetails) (#1793) --- slack_sdk/web/async_client.py | 7 ++++--- slack_sdk/web/client.py | 7 ++++--- slack_sdk/web/internal_utils.py | 6 +++++- slack_sdk/web/legacy_client.py | 7 ++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index 050e93294..cedaa86d4 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -22,7 +22,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata, EventAndEntityMetadata +from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .async_base_client import AsyncBaseClient, AsyncSlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -3650,10 +3650,10 @@ async def emoji_list( async def entity_presentDetails( self, trigger_id: str, - metadata: Optional[dict] = None, + metadata: Optional[Union[Dict, EntityMetadata]] = None, user_auth_required: Optional[bool] = None, user_auth_url: Optional[str] = None, - error: Optional[Dict[str, str]] = None, + error: Optional[Dict[str, Any]] = None, **kwargs, ) -> AsyncSlackResponse: """Provides entity details for the flexpane. @@ -3668,6 +3668,7 @@ async def entity_presentDetails( kwargs.update({"user_auth_url": user_auth_url}) if error is not None: kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) return await self.api_call("entity.presentDetails", json=kwargs) async def files_comments_delete( diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 830f481d0..4be33e8df 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -12,7 +12,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata, EventAndEntityMetadata +from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .base_client import BaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -3640,10 +3640,10 @@ def emoji_list( def entity_presentDetails( self, trigger_id: str, - metadata: Optional[dict] = None, + metadata: Optional[Union[Dict, EntityMetadata]] = None, user_auth_required: Optional[bool] = None, user_auth_url: Optional[str] = None, - error: Optional[Dict[str, str]] = None, + error: Optional[Dict[str, Any]] = None, **kwargs, ) -> SlackResponse: """Provides entity details for the flexpane. @@ -3658,6 +3658,7 @@ def entity_presentDetails( kwargs.update({"user_auth_url": user_auth_url}) if error is not None: kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) return self.api_call("entity.presentDetails", json=kwargs) def files_comments_delete( diff --git a/slack_sdk/web/internal_utils.py b/slack_sdk/web/internal_utils.py index 57afc6be4..87139559c 100644 --- a/slack_sdk/web/internal_utils.py +++ b/slack_sdk/web/internal_utils.py @@ -212,7 +212,11 @@ def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata kwargs.update({"attachments": dict_attachments}) metadata = kwargs.get("metadata", None) - if metadata is not None and (isinstance(metadata, Metadata) or isinstance(metadata, EventAndEntityMetadata)): + if metadata is not None and ( + isinstance(metadata, Metadata) + or isinstance(metadata, EntityMetadata) + or isinstance(metadata, EventAndEntityMetadata) + ): kwargs.update({"metadata": to_dict(metadata)}) diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index cce4bd1ad..3aaa36f74 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -23,7 +23,7 @@ from ..models.attachments import Attachment from ..models.blocks import Block -from ..models.metadata import Metadata, EventAndEntityMetadata +from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .legacy_base_client import LegacyBaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -3588,10 +3588,10 @@ def emoji_list( def entity_presentDetails( self, trigger_id: str, - metadata: Optional[dict] = None, + metadata: Optional[Union[Dict, EntityMetadata]] = None, user_auth_required: Optional[bool] = None, user_auth_url: Optional[str] = None, - error: Optional[Dict[str, str]] = None, + error: Optional[Dict[str, Any]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Provides entity details for the flexpane. @@ -3606,6 +3606,7 @@ def entity_presentDetails( kwargs.update({"user_auth_url": user_auth_url}) if error is not None: kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) return self.api_call("entity.presentDetails", json=kwargs) def files_comments_delete( From c04e899f9e153fc102cf699d1b044202022148c1 Mon Sep 17 00:00:00 2001 From: Maria Alejandra <104795114+srtaalej@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:39:36 -0500 Subject: [PATCH 05/30] feat(web-api): add slackLists methods (#1772) Co-authored-by: Eden Zimbelman --- integration_tests/web/test_slack_lists.py | 88 +++++++ slack_sdk/web/async_client.py | 245 +++++++++++++++++- slack_sdk/web/client.py | 245 +++++++++++++++++- slack_sdk/web/legacy_client.py | 245 +++++++++++++++++- .../web/test_web_client_coverage.py | 40 ++- 5 files changed, 859 insertions(+), 4 deletions(-) create mode 100644 integration_tests/web/test_slack_lists.py diff --git a/integration_tests/web/test_slack_lists.py b/integration_tests/web/test_slack_lists.py new file mode 100644 index 000000000..6d813bffe --- /dev/null +++ b/integration_tests/web/test_slack_lists.py @@ -0,0 +1,88 @@ +import logging +import os +import unittest + +from integration_tests.env_variable_names import ( + SLACK_SDK_TEST_BOT_TOKEN, +) +from integration_tests.helpers import async_test +from slack_sdk.web import WebClient +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.models.blocks import RichTextBlock +from slack_sdk.models.blocks.block_elements import RichTextSection, RichTextText + + +class TestSlackLists(unittest.TestCase): + """Runs integration tests with real Slack API testing the slackLists.* APIs""" + + def setUp(self): + if not hasattr(self, "logger"): + self.logger = logging.getLogger(__name__) + self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] + self.async_client: AsyncWebClient = AsyncWebClient(token=self.bot_token) + self.sync_client: WebClient = WebClient(token=self.bot_token) + + def tearDown(self): + pass + + def test_create_list_with_dicts(self): + """Test creating a list with description_blocks as dicts""" + client = self.sync_client + + create_response = client.slackLists_create( + name="Test Sales Pipeline", + description_blocks=[ + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "This is a test list for integration testing"}], + } + ], + } + ], + schema=[ + {"key": "deal_name", "name": "Deal Name", "type": "text", "is_primary_column": True}, + {"key": "amount", "name": "Amount", "type": "number", "options": {"format": "currency", "precision": 2}}, + ], + ) + + self.assertIsNotNone(create_response) + self.assertTrue(create_response["ok"]) + self.assertIn("list", create_response) + list_id = create_response["list"]["id"] + self.logger.info(f"✓ Created list with ID: {list_id}") + + def test_create_list_with_rich_text_blocks(self): + """Test creating a list with RichTextBlock objects""" + client = self.sync_client + + create_response = client.slackLists_create( + name="Test List with Rich Text Blocks", + description_blocks=[ + RichTextBlock( + elements=[RichTextSection(elements=[RichTextText(text="Created with RichTextBlock objects!")])] + ) + ], + schema=[{"key": "task_name", "name": "Task", "type": "text", "is_primary_column": True}], + ) + + self.assertIsNotNone(create_response) + self.assertTrue(create_response["ok"]) + list_id = create_response["list"]["id"] + self.logger.info(f"✓ Created list with RichTextBlocks, ID: {list_id}") + + @async_test + async def test_create_list_async(self): + """Test creating a list with async client""" + client = self.async_client + + create_response = await client.slackLists_create( + name="Async Test List", schema=[{"key": "item_name", "name": "Item", "type": "text", "is_primary_column": True}] + ) + + self.assertIsNotNone(create_response) + self.assertTrue(create_response["ok"]) + list_id = create_response["list"]["id"] + self.logger.info(f"✓ Created list asynchronously, ID: {list_id}") diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index cedaa86d4..ca163da98 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -21,7 +21,7 @@ from slack_sdk.web.async_chat_stream import AsyncChatStream from ..models.attachments import Attachment -from ..models.blocks import Block +from ..models.blocks import Block, RichTextBlock from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .async_base_client import AsyncBaseClient, AsyncSlackResponse from .internal_utils import ( @@ -4925,6 +4925,249 @@ async def search_messages( ) return await self.api_call("search.messages", http_verb="GET", params=kwargs) + async def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.access.delete", json=kwargs) + + async def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.access.set", json=kwargs) + + async def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.create", json=kwargs) + + async def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> AsyncSlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.download.get", json=kwargs) + + async def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.download.start", json=kwargs) + + async def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.create", json=kwargs) + + async def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> AsyncSlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.delete", json=kwargs) + + async def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> AsyncSlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + async def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.info", json=kwargs) + + async def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.list", json=kwargs) + + async def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> AsyncSlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.update", json=kwargs) + + async def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.update", json=kwargs) + async def stars_add( self, *, diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 4be33e8df..dfa771832 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -11,7 +11,7 @@ from slack_sdk.web.chat_stream import ChatStream from ..models.attachments import Attachment -from ..models.blocks import Block +from ..models.blocks import Block, RichTextBlock from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .base_client import BaseClient, SlackResponse from .internal_utils import ( @@ -4915,6 +4915,249 @@ def search_messages( ) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> SlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> SlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> SlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> SlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> SlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index 3aaa36f74..df2bcc370 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -22,7 +22,7 @@ from slack_sdk.models.views import View from ..models.attachments import Attachment -from ..models.blocks import Block +from ..models.blocks import Block, RichTextBlock from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata from .legacy_base_client import LegacyBaseClient, SlackResponse from .internal_utils import ( @@ -4863,6 +4863,249 @@ def search_messages( ) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> Union[Future, SlackResponse]: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> Union[Future, SlackResponse]: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, diff --git a/tests/slack_sdk_async/web/test_web_client_coverage.py b/tests/slack_sdk_async/web/test_web_client_coverage.py index af6d92236..0a3c1687b 100644 --- a/tests/slack_sdk_async/web/test_web_client_coverage.py +++ b/tests/slack_sdk_async/web/test_web_client_coverage.py @@ -15,7 +15,7 @@ class TestWebClientCoverage(unittest.TestCase): # 295 endpoints as of September 17, 2025 # Can be fetched by running `var methodNames = [].slice.call(document.getElementsByClassName('apiReferenceFilterableList__listItemLink')).map(e => e.href.replace("https://api.slack.com/methods/", ""));console.log(methodNames.toString());console.log(methodNames.length);` on https://api.slack.com/methods - all_api_methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.uninstall,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.appendStream,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.scheduledMessages.list,chat.startStream,chat.stopStream,chat.unfurl,chat.update,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.comments.delete,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark".split( + all_api_methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.uninstall,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.appendStream,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.scheduledMessages.list,chat.startStream,chat.stopStream,chat.unfurl,chat.update,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.comments.delete,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,slackLists.access.delete,slackLists.access.set,slackLists.create,slackLists.download.get,slackLists.download.start,slackLists.items.create,slackLists.items.delete,slackLists.items.delete,slackLists.items.deleteMultiple,slackLists.items.info,slackLists.items.list,slackLists.items.update,slackLists.update,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark".split( "," ) @@ -802,6 +802,44 @@ async def run_method(self, method_name, method, async_method): elif method_name == "search_messages": self.api_methods_to_call.remove(method(query="Slack")["method"]) await async_method(query="Slack") + elif method_name == "slackLists_access_delete": + self.api_methods_to_call.remove(method(list_id="123")["method"]) + await async_method(list_id="123") + elif method_name == "slackLists_access_set": + self.api_methods_to_call.remove(method(list_id="123", access_level="private")["method"]) + await async_method(list_id="123", access_level="private") + elif method_name == "slackLists_create": + self.api_methods_to_call.remove(method(name="Backlog")["method"]) + await async_method(name="Backlog") + elif method_name == "slackLists_download_get": + self.api_methods_to_call.remove(method(list_id="123", job_id="123")["method"]) + await async_method(list_id="123", job_id="123") + elif method_name == "slackLists_download_start": + self.api_methods_to_call.remove(method(list_id="123")["method"]) + await async_method(list_id="123") + elif method_name == "slackLists_items_create": + self.api_methods_to_call.remove(method(list_id="123")["method"]) + await async_method(list_id="123") + elif method_name == "slackLists_items_delete": + self.api_methods_to_call.remove(method(list_id="123", id="123")["method"]) + await async_method(list_id="123", id="123") + elif method_name == "slackLists_items_deleteMultiple": + self.api_methods_to_call.remove(method(list_id="123", ids=["123", "456"])["method"]) + await async_method(list_id="123", ids=["123", "456"]) + elif method_name == "slackLists_items_info": + self.api_methods_to_call.remove(method(list_id="123", id="123")["method"]) + await async_method(list_id="123", id="123") + elif method_name == "slackLists_items_list": + self.api_methods_to_call.remove(method(list_id="123")["method"]) + await async_method(list_id="123") + elif method_name == "slackLists_items_update": + self.api_methods_to_call.remove( + method(list_id="123", cells=[{"column_id": "col123"}, {"row_id": "row123"}])["method"] + ) + await async_method(list_id="123", cells=[{"column_id": "col123"}, {"row_id": "row123"}]) + elif method_name == "slackLists_update": + self.api_methods_to_call.remove(method(id="123")["method"]) + await async_method(id="123") elif method_name == "team_externalTeams_disconnect": self.api_methods_to_call.remove(method(target_team="T111")["method"]) await async_method(target_team="T111") From 9fd33fe2d142ad53c699c6c61d99dc23baa34699 Mon Sep 17 00:00:00 2001 From: Gritty_dev <101377478+codomposer@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:52:16 -0500 Subject: [PATCH 06/30] feat: add table block (#1788) Co-authored-by: Eden Zimbelman --- slack_sdk/models/blocks/__init__.py | 4 + slack_sdk/models/blocks/basic_components.py | 34 ++++ slack_sdk/models/blocks/blocks.py | 46 +++++ tests/slack_sdk/models/test_blocks.py | 202 ++++++++++++++++++++ 4 files changed, 286 insertions(+) diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index 334f55c40..d2776a9dc 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -16,6 +16,7 @@ Option, OptionGroup, PlainTextObject, + RawTextObject, TextObject, ) from .block_elements import ( @@ -71,6 +72,7 @@ MarkdownBlock, RichTextBlock, SectionBlock, + TableBlock, VideoBlock, ) @@ -83,6 +85,7 @@ "Option", "OptionGroup", "PlainTextObject", + "RawTextObject", "TextObject", "BlockElement", "ButtonElement", @@ -133,6 +136,7 @@ "InputBlock", "MarkdownBlock", "SectionBlock", + "TableBlock", "VideoBlock", "RichTextBlock", ] diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py index 6f22c88e1..b6e71683a 100644 --- a/slack_sdk/models/blocks/basic_components.py +++ b/slack_sdk/models/blocks/basic_components.py @@ -157,6 +157,40 @@ def direct_from_link(link: Link, title: str = "") -> Dict[str, Any]: return MarkdownTextObject.from_link(link, title).to_dict() +class RawTextObject(TextObject): + """raw_text typed text object""" + + type = "raw_text" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return {"text", "type"} + + def __init__(self, *, text: str): + """A raw text object used in table block cells. + https://docs.slack.dev/reference/block-kit/composition-objects/text-object/ + https://docs.slack.dev/reference/block-kit/blocks/table-block + + Args: + text (required): The text content for the table block cell. + """ + super().__init__(text=text, type=self.type) + + @staticmethod + def from_str(text: str) -> "RawTextObject": + """Transforms a string into a RawTextObject""" + return RawTextObject(text=text) + + @staticmethod + def direct_from_string(text: str) -> Dict[str, Any]: + """Transforms a string into the required object shape to act as a RawTextObject""" + return RawTextObject.from_str(text).to_dict() + + @JsonValidator("text attribute must have at least 1 character") + def _validate_text_min_length(self): + return len(self.text) >= 1 + + 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 diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index 9976de5b4..cac463c99 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -95,6 +95,8 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]: return VideoBlock(**block) elif type == RichTextBlock.type: return RichTextBlock(**block) + elif type == TableBlock.type: + return TableBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -731,3 +733,47 @@ def __init__( show_unknown_key_warning(self, others) self.elements = BlockElement.parse_all(elements) + + +class TableBlock(Block): + type = "table" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"rows", "column_settings"}) + + def __init__( + self, + *, + rows: Sequence[Sequence[Dict[str, Any]]], + column_settings: Optional[Sequence[Optional[Dict[str, Any]]]] = None, + block_id: Optional[str] = None, + **others: dict, + ): + """Displays structured information in a table. + https://docs.slack.dev/reference/block-kit/blocks/table-block + + 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. + 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. + Any additional columns will have the default behavior. Maximum 20 items. + See below for column settings schema. + block_id: A unique identifier for a block. If not specified, a block_id will be generated. + You can use this block_id when you receive an interaction payload to identify the source of the action. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.rows = rows + self.column_settings = column_settings + + @JsonValidator("rows attribute must be specified") + def _validate_rows(self): + return self.rows is not None and len(self.rows) > 0 diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index a07ce11b8..6f3b9f141 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -21,6 +21,7 @@ Option, OverflowMenuElement, PlainTextObject, + RawTextObject, RichTextBlock, RichTextElementParts, RichTextListElement, @@ -29,6 +30,7 @@ RichTextSectionElement, SectionBlock, StaticSelectElement, + TableBlock, VideoBlock, ) from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile @@ -1267,3 +1269,203 @@ def test_parsing_empty_block_elements(self): self.assertIsNotNone(block_dict["elements"][1].get("elements")) self.assertIsNotNone(block_dict["elements"][2].get("elements")) self.assertIsNotNone(block_dict["elements"][3].get("elements")) + + +# ---------------------------------------------- +# RawTextObject +# ---------------------------------------------- + + +class RawTextObjectTests(unittest.TestCase): + def test_basic_creation(self): + """Test basic RawTextObject creation""" + obj = RawTextObject(text="Hello") + expected = {"type": "raw_text", "text": "Hello"} + self.assertDictEqual(expected, obj.to_dict()) + + def test_from_str(self): + """Test RawTextObject.from_str() helper""" + obj = RawTextObject.from_str("Test text") + expected = {"type": "raw_text", "text": "Test text"} + self.assertDictEqual(expected, obj.to_dict()) + + def test_direct_from_string(self): + """Test RawTextObject.direct_from_string() helper""" + result = RawTextObject.direct_from_string("Direct text") + expected = {"type": "raw_text", "text": "Direct text"} + self.assertDictEqual(expected, result) + + def test_text_length_validation_min(self): + """Test that empty text fails validation""" + with self.assertRaises(SlackObjectFormationError): + RawTextObject(text="").to_dict() + + def test_text_length_validation_at_min(self): + """Test that text with 1 character passes validation""" + obj = RawTextObject(text="a") + obj.to_dict() # Should not raise + + def test_attributes(self): + """Test that RawTextObject only has text and type attributes""" + obj = RawTextObject(text="Test") + self.assertEqual(obj.attributes, {"text", "type"}) + # Should not have emoji attribute like PlainTextObject + self.assertNotIn("emoji", obj.to_dict()) + + +# ---------------------------------------------- +# Table +# ---------------------------------------------- + + +class TableBlockTests(unittest.TestCase): + def test_document(self): + """Test basic table block from Slack documentation example""" + input = { + "type": "table", + "column_settings": [{"is_wrapped": True}, {"align": "right"}], + "rows": [ + [{"type": "raw_text", "text": "Header A"}, {"type": "raw_text", "text": "Header B"}], + [{"type": "raw_text", "text": "Data 1A"}, {"type": "raw_text", "text": "Data 1B"}], + [{"type": "raw_text", "text": "Data 2A"}, {"type": "raw_text", "text": "Data 2B"}], + ], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + def test_with_rich_text(self): + """Test table block with rich_text cells""" + input = { + "type": "table", + "column_settings": [{"is_wrapped": True}, {"align": "right"}], + "rows": [ + [{"type": "raw_text", "text": "Header A"}, {"type": "raw_text", "text": "Header B"}], + [ + {"type": "raw_text", "text": "Data 1A"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"text": "Data 1B", "type": "link", "url": "https://slack.com"}], + } + ], + }, + ], + ], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + def test_minimal_table(self): + """Test table with only required fields""" + input = { + "type": "table", + "rows": [[{"type": "raw_text", "text": "Cell"}]], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + + def test_with_block_id(self): + """Test table block with block_id""" + input = { + "type": "table", + "block_id": "table-123", + "rows": [ + [{"type": "raw_text", "text": "A"}, {"type": "raw_text", "text": "B"}], + [{"type": "raw_text", "text": "1"}, {"type": "raw_text", "text": "2"}], + ], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + + def test_column_settings_variations(self): + """Test various column_settings configurations""" + # Left align + input1 = { + "type": "table", + "column_settings": [{"align": "left"}], + "rows": [[{"type": "raw_text", "text": "Left"}]], + } + self.assertDictEqual(input1, TableBlock(**input1).to_dict()) + + # Center align + input2 = { + "type": "table", + "column_settings": [{"align": "center"}], + "rows": [[{"type": "raw_text", "text": "Center"}]], + } + self.assertDictEqual(input2, TableBlock(**input2).to_dict()) + + # With wrapping + input3 = { + "type": "table", + "column_settings": [{"is_wrapped": False}], + "rows": [[{"type": "raw_text", "text": "No wrap"}]], + } + self.assertDictEqual(input3, TableBlock(**input3).to_dict()) + + # Combined settings + input4 = { + "type": "table", + "column_settings": [{"align": "center", "is_wrapped": True}], + "rows": [[{"type": "raw_text", "text": "Both"}]], + } + self.assertDictEqual(input4, TableBlock(**input4).to_dict()) + + def test_column_settings_with_none(self): + """Test column_settings with None to skip columns""" + input = { + "type": "table", + "column_settings": [{"align": "left"}, None, {"align": "right"}], + "rows": [ + [ + {"type": "raw_text", "text": "Left"}, + {"type": "raw_text", "text": "Default"}, + {"type": "raw_text", "text": "Right"}, + ] + ], + } + self.assertDictEqual(input, TableBlock(**input).to_dict()) + + def test_rows_validation(self): + """Test that rows validation works correctly""" + # Empty rows should fail validation + with self.assertRaises(SlackObjectFormationError): + TableBlock(rows=[]).to_dict() + + def test_multi_row_table(self): + """Test table with multiple rows""" + input = { + "type": "table", + "rows": [ + [{"type": "raw_text", "text": "Name"}, {"type": "raw_text", "text": "Age"}], + [{"type": "raw_text", "text": "Alice"}, {"type": "raw_text", "text": "30"}], + [{"type": "raw_text", "text": "Bob"}, {"type": "raw_text", "text": "25"}], + [{"type": "raw_text", "text": "Charlie"}, {"type": "raw_text", "text": "35"}], + ], + } + block = TableBlock(**input) + self.assertEqual(len(block.rows), 4) + self.assertDictEqual(input, block.to_dict()) + + def test_with_raw_text_object_helper(self): + """Test table using RawTextObject helper class""" + # Create table using RawTextObject helper + block = TableBlock( + rows=[ + [RawTextObject(text="Product").to_dict(), RawTextObject(text="Price").to_dict()], + [RawTextObject(text="Widget").to_dict(), RawTextObject(text="$10").to_dict()], + [RawTextObject(text="Gadget").to_dict(), RawTextObject(text="$20").to_dict()], + ], + column_settings=[{"is_wrapped": True}, {"align": "right"}], + ) + + expected = { + "type": "table", + "column_settings": [{"is_wrapped": True}, {"align": "right"}], + "rows": [ + [{"type": "raw_text", "text": "Product"}, {"type": "raw_text", "text": "Price"}], + [{"type": "raw_text", "text": "Widget"}, {"type": "raw_text", "text": "$10"}], + [{"type": "raw_text", "text": "Gadget"}, {"type": "raw_text", "text": "$20"}], + ], + } + self.assertDictEqual(expected, block.to_dict()) From 978dd5617f3c885142db8d968b03e26bc19ba135 Mon Sep 17 00:00:00 2001 From: Elaine Vegeris Date: Thu, 20 Nov 2025 10:05:22 -0500 Subject: [PATCH 07/30] chore(release): version 3.39.0 (#1795) --- docs/reference/http_retry/async_handler.html | 2 +- docs/reference/http_retry/index.html | 2 +- docs/reference/http_retry/response.html | 2 +- docs/reference/index.html | 702 ++++- docs/reference/models/basic_objects.html | 62 +- .../models/blocks/basic_components.html | 134 + docs/reference/models/blocks/blocks.html | 127 + docs/reference/models/blocks/index.html | 261 ++ docs/reference/models/index.html | 62 +- docs/reference/models/metadata/index.html | 2540 +++++++++++++++++ docs/reference/web/async_client.html | 702 ++++- docs/reference/web/client.html | 702 ++++- docs/reference/web/index.html | 702 ++++- docs/reference/web/legacy_client.html | 702 ++++- slack_sdk/version.py | 2 +- 15 files changed, 6672 insertions(+), 32 deletions(-) diff --git a/docs/reference/http_retry/async_handler.html b/docs/reference/http_retry/async_handler.html index 169110894..4f3889fcc 100644 --- a/docs/reference/http_retry/async_handler.html +++ b/docs/reference/http_retry/async_handler.html @@ -193,7 +193,7 @@

Static methods

class HttpResponse -(*,
status_code: str | int,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
+(*,
status_code: int | str,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
diff --git a/docs/reference/http_retry/index.html b/docs/reference/http_retry/index.html index 7e2294404..501a62c9e 100644 --- a/docs/reference/http_retry/index.html +++ b/docs/reference/http_retry/index.html @@ -388,7 +388,7 @@

Static methods

class HttpResponse -(*,
status_code: str | int,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
+(*,
status_code: int | str,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
diff --git a/docs/reference/http_retry/response.html b/docs/reference/http_retry/response.html index d9c82b4d8..4a786ab78 100644 --- a/docs/reference/http_retry/response.html +++ b/docs/reference/http_retry/response.html @@ -48,7 +48,7 @@

Classes

class HttpResponse -(*,
status_code: str | int,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
+(*,
status_code: int | str,
headers: Dict[str, str | List[str]],
body: Dict[str, Any] | None = None,
data: bytes | None = None)
diff --git a/docs/reference/index.html b/docs/reference/index.html index 282903146..7cc7723c1 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -2894,7 +2894,7 @@

Classes

link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -3123,6 +3123,7 @@

Classes

source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3139,6 +3140,7 @@

Classes

"source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3770,6 +3772,30 @@

Classes

kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[Union[Dict, EntityMetadata]] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> SlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, @@ -5024,6 +5050,249 @@

Classes

) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> SlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> SlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> SlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> SlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> SlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, @@ -10170,7 +10439,7 @@

Methods

https://docs.slack.dev/reference/methods/chat.postEphemeral

-def chat_postMessage(self,
*,
channel: str,
text: str | None = None,
as_user: bool | None = None,
attachments: str | Sequence[Dict | Attachment] | None = None,
blocks: str | Sequence[Dict | Block] | None = None,
thread_ts: str | None = None,
reply_broadcast: bool | None = None,
unfurl_links: bool | None = None,
unfurl_media: bool | None = None,
container_id: str | None = None,
icon_emoji: str | None = None,
icon_url: str | None = None,
mrkdwn: bool | None = None,
link_names: bool | None = None,
username: str | None = None,
parse: str | None = None,
metadata: Dict | Metadata | None = None,
markdown_text: str | None = None,
**kwargs) ‑> SlackResponse
+def chat_postMessage(self,
*,
channel: str,
text: str | None = None,
as_user: bool | None = None,
attachments: str | Sequence[Dict | Attachment] | None = None,
blocks: str | Sequence[Dict | Block] | None = None,
thread_ts: str | None = None,
reply_broadcast: bool | None = None,
unfurl_links: bool | None = None,
unfurl_media: bool | None = None,
container_id: str | None = None,
icon_emoji: str | None = None,
icon_url: str | None = None,
mrkdwn: bool | None = None,
link_names: bool | None = None,
username: str | None = None,
parse: str | None = None,
metadata: Dict | Metadata | EventAndEntityMetadata | None = None,
markdown_text: str | None = None,
**kwargs) ‑> SlackResponse
@@ -10196,7 +10465,7 @@

Methods

link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -10518,7 +10787,7 @@

Example

-def chat_unfurl(self,
*,
channel: str | None = None,
ts: str | None = None,
source: str | None = None,
unfurl_id: str | None = None,
unfurls: Dict[str, Dict] | None = None,
user_auth_blocks: str | Sequence[Dict | Block] | None = None,
user_auth_message: str | None = None,
user_auth_required: bool | None = None,
user_auth_url: str | None = None,
**kwargs) ‑> SlackResponse
+def chat_unfurl(self,
*,
channel: str | None = None,
ts: str | None = None,
source: str | None = None,
unfurl_id: str | None = None,
unfurls: Dict[str, Dict] | None = None,
metadata: Dict | EventAndEntityMetadata | None = None,
user_auth_blocks: str | Sequence[Dict | Block] | None = None,
user_auth_message: str | None = None,
user_auth_required: bool | None = None,
user_auth_url: str | None = None,
**kwargs) ‑> SlackResponse
@@ -10533,6 +10802,7 @@

Example

source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -10549,6 +10819,7 @@

Example

"source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -11580,6 +11851,41 @@

Example

+
+def entity_presentDetails(self,
trigger_id: str,
metadata: Dict | EntityMetadata | None = None,
user_auth_required: bool | None = None,
user_auth_url: str | None = None,
error: Dict[str, Any] | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def entity_presentDetails(
+    self,
+    trigger_id: str,
+    metadata: Optional[Union[Dict, EntityMetadata]] = None,
+    user_auth_required: Optional[bool] = None,
+    user_auth_url: Optional[str] = None,
+    error: Optional[Dict[str, Any]] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Provides entity details for the flexpane.
+    https://docs.slack.dev/reference/methods/entity.presentDetails/
+    """
+    kwargs.update({"trigger_id": trigger_id})
+    if metadata is not None:
+        kwargs.update({"metadata": metadata})
+    if user_auth_required is not None:
+        kwargs.update({"user_auth_required": user_auth_required})
+    if user_auth_url is not None:
+        kwargs.update({"user_auth_url": user_auth_url})
+    if error is not None:
+        kwargs.update({"error": error})
+    _parse_web_class_objects(kwargs)
+    return self.api_call("entity.presentDetails", json=kwargs)
+
+

Provides entity details for the flexpane. +https://docs.slack.dev/reference/methods/entity.presentDetails/

+
def files_comments_delete(self, *, file: str, id: str, **kwargs) ‑> SlackResponse
@@ -13555,6 +13861,381 @@

Example

Searches for messages matching a query. https://docs.slack.dev/reference/methods/search.messages

+
+def slackLists_access_delete(self,
*,
list_id: str,
channel_ids: List[str] | None = None,
user_ids: List[str] | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_access_delete(
+    self,
+    *,
+    list_id: str,
+    channel_ids: Optional[List[str]] = None,
+    user_ids: Optional[List[str]] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Revoke access to a List for specified entities.
+    https://docs.slack.dev/reference/methods/slackLists.access.delete
+    """
+    kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids})
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.access.delete", json=kwargs)
+
+

Revoke access to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.delete

+
+
+def slackLists_access_set(self,
*,
list_id: str,
access_level: str,
channel_ids: List[str] | None = None,
user_ids: List[str] | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_access_set(
+    self,
+    *,
+    list_id: str,
+    access_level: str,
+    channel_ids: Optional[List[str]] = None,
+    user_ids: Optional[List[str]] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Set the access level to a List for specified entities.
+    https://docs.slack.dev/reference/methods/slackLists.access.set
+    """
+    kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids})
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.access.set", json=kwargs)
+
+

Set the access level to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.set

+
+
+def slackLists_create(self,
*,
name: str,
description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
schema: List[Dict[str, Any]] | None = None,
copy_from_list_id: str | None = None,
include_copied_list_records: bool | None = None,
todo_mode: bool | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_create(
+    self,
+    *,
+    name: str,
+    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
+    schema: Optional[List[Dict[str, Any]]] = None,
+    copy_from_list_id: Optional[str] = None,
+    include_copied_list_records: Optional[bool] = None,
+    todo_mode: Optional[bool] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Creates a List.
+    https://docs.slack.dev/reference/methods/slackLists.create
+    """
+    kwargs.update(
+        {
+            "name": name,
+            "description_blocks": description_blocks,
+            "schema": schema,
+            "copy_from_list_id": copy_from_list_id,
+            "include_copied_list_records": include_copied_list_records,
+            "todo_mode": todo_mode,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.create", json=kwargs)
+
+ +
+
+def slackLists_download_get(self, *, list_id: str, job_id: str, **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_download_get(
+    self,
+    *,
+    list_id: str,
+    job_id: str,
+    **kwargs,
+) -> SlackResponse:
+    """Retrieve List download URL from an export job to download List contents.
+    https://docs.slack.dev/reference/methods/slackLists.download.get
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "job_id": job_id,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.download.get", json=kwargs)
+
+

Retrieve List download URL from an export job to download List contents. +https://docs.slack.dev/reference/methods/slackLists.download.get

+
+
+def slackLists_download_start(self, *, list_id: str, include_archived: bool | None = None, **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_download_start(
+    self,
+    *,
+    list_id: str,
+    include_archived: Optional[bool] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Initiate a job to export List contents.
+    https://docs.slack.dev/reference/methods/slackLists.download.start
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "include_archived": include_archived,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.download.start", json=kwargs)
+
+ +
+
+def slackLists_items_create(self,
*,
list_id: str,
duplicated_item_id: str | None = None,
parent_item_id: str | None = None,
initial_fields: List[Dict[str, Any]] | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_items_create(
+    self,
+    *,
+    list_id: str,
+    duplicated_item_id: Optional[str] = None,
+    parent_item_id: Optional[str] = None,
+    initial_fields: Optional[List[Dict[str, Any]]] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Add a new item to an existing List.
+    https://docs.slack.dev/reference/methods/slackLists.items.create
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "duplicated_item_id": duplicated_item_id,
+            "parent_item_id": parent_item_id,
+            "initial_fields": initial_fields,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.create", json=kwargs)
+
+ +
+
+def slackLists_items_delete(self, *, list_id: str, id: str, **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_items_delete(
+    self,
+    *,
+    list_id: str,
+    id: str,
+    **kwargs,
+) -> SlackResponse:
+    """Deletes an item from an existing List.
+    https://docs.slack.dev/reference/methods/slackLists.items.delete
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "id": id,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.delete", json=kwargs)
+
+ +
+
+def slackLists_items_deleteMultiple(self, *, list_id: str, ids: List[str], **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_items_deleteMultiple(
+    self,
+    *,
+    list_id: str,
+    ids: List[str],
+    **kwargs,
+) -> SlackResponse:
+    """Deletes multiple items from an existing List.
+    https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "ids": ids,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.deleteMultiple", json=kwargs)
+
+ +
+
+def slackLists_items_info(self, *, list_id: str, id: str, include_is_subscribed: bool | None = None, **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_items_info(
+    self,
+    *,
+    list_id: str,
+    id: str,
+    include_is_subscribed: Optional[bool] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Get a row from a List.
+    https://docs.slack.dev/reference/methods/slackLists.items.info
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "id": id,
+            "include_is_subscribed": include_is_subscribed,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.info", json=kwargs)
+
+ +
+
+def slackLists_items_list(self,
*,
list_id: str,
limit: int | None = None,
cursor: str | None = None,
archived: bool | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_items_list(
+    self,
+    *,
+    list_id: str,
+    limit: Optional[int] = None,
+    cursor: Optional[str] = None,
+    archived: Optional[bool] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Get records from a List.
+    https://docs.slack.dev/reference/methods/slackLists.items.list
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "limit": limit,
+            "cursor": cursor,
+            "archived": archived,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.list", json=kwargs)
+
+ +
+
+def slackLists_items_update(self, *, list_id: str, cells: List[Dict[str, Any]], **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def slackLists_items_update(
+    self,
+    *,
+    list_id: str,
+    cells: List[Dict[str, Any]],
+    **kwargs,
+) -> SlackResponse:
+    """Updates cells in a List.
+    https://docs.slack.dev/reference/methods/slackLists.items.update
+    """
+    kwargs.update(
+        {
+            "list_id": list_id,
+            "cells": cells,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.items.update", json=kwargs)
+
+ +
+
+def slackLists_update(self,
*,
id: str,
name: str | None = None,
description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
todo_mode: bool | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def slackLists_update(
+    self,
+    *,
+    id: str,
+    name: Optional[str] = None,
+    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
+    todo_mode: Optional[bool] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Update a List.
+    https://docs.slack.dev/reference/methods/slackLists.update
+    """
+    kwargs.update(
+        {
+            "id": id,
+            "name": name,
+            "description_blocks": description_blocks,
+            "todo_mode": todo_mode,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("slackLists.update", json=kwargs)
+
+ +
def stars_add(self,
*,
channel: str | None = None,
file: str | None = None,
file_comment: str | None = None,
timestamp: str | None = None,
**kwargs) ‑> SlackResponse
@@ -15532,6 +16213,7 @@

WebClientdnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15601,6 +16283,18 @@

    WebClientsearch_all
  • search_files
  • search_messages
  • +
  • slackLists_access_delete
  • +
  • slackLists_access_set
  • +
  • slackLists_create
  • +
  • slackLists_download_get
  • +
  • slackLists_download_start
  • +
  • slackLists_items_create
  • +
  • slackLists_items_delete
  • +
  • slackLists_items_deleteMultiple
  • +
  • slackLists_items_info
  • +
  • slackLists_items_list
  • +
  • slackLists_items_update
  • +
  • slackLists_update
  • stars_add
  • stars_list
  • stars_remove
  • diff --git a/docs/reference/models/basic_objects.html b/docs/reference/models/basic_objects.html index 972273cc9..193200f84 100644 --- a/docs/reference/models/basic_objects.html +++ b/docs/reference/models/basic_objects.html @@ -119,6 +119,9 @@

    Ancestors

    if callable(method) and hasattr(method, "validator"): method() + def get_object_attribute(self, key: str): + return getattr(self, key, None) + def get_non_null_attributes(self) -> dict: """ Construct a dictionary out of non-null keys (from attributes property) @@ -136,7 +139,7 @@

    Ancestors

    return value def is_not_empty(self, key: str) -> bool: - value = getattr(self, key, None) + value = self.get_object_attribute(key) if value is None: return False @@ -154,7 +157,9 @@

    Ancestors

    return value is not None return { - key: to_dict_compatible(getattr(self, key, None)) for key in sorted(self.attributes) if is_not_empty(self, key) + key: to_dict_compatible(value=self.get_object_attribute(key)) + for key in sorted(self.attributes) + if is_not_empty(self, key) } def to_dict(self, *args) -> dict: @@ -208,7 +213,40 @@

    Subclasses

  • DialogBuilder
  • DialogTextComponent
  • Message
  • +
  • ContentItemEntityFields
  • +
  • EntityActionButton
  • +
  • EntityActionProcessingState
  • +
  • EntityActions
  • +
  • EntityArrayItemField
  • +
  • EntityAttributes
  • +
  • EntityBooleanCheckboxField
  • +
  • EntityBooleanTextField
  • +
  • EntityCustomField
  • +
  • EntityEditNumberConfig
  • +
  • EntityEditSelectConfig
  • +
  • EntityEditSupport
  • +
  • EntityEditTextConfig
  • +
  • EntityFullSizePreview
  • +
  • EntityFullSizePreviewError
  • +
  • EntityIconField
  • +
  • EntityIconSlackFile
  • +
  • EntityImageField
  • +
  • EntityMetadata
  • +
  • EntityPayload
  • +
  • EntityRefField
  • +
  • EntityStringField
  • +
  • EntityTimestampField
  • +
  • EntityTitle
  • +
  • EntityTypedField
  • +
  • EntityUserField
  • +
  • EntityUserIDField
  • +
  • EventAndEntityMetadata
  • +
  • ExternalRef
  • +
  • FileEntityFields
  • +
  • FileEntitySlackFile
  • +
  • IncidentEntityFields
  • Metadata
  • +
  • TaskEntityFields
  • View
  • ViewState
  • ViewStateValue
  • @@ -257,7 +295,7 @@

    Methods

    return value def is_not_empty(self, key: str) -> bool: - value = getattr(self, key, None) + value = self.get_object_attribute(key) if value is None: return False @@ -275,12 +313,27 @@

    Methods

    return value is not None return { - key: to_dict_compatible(getattr(self, key, None)) for key in sorted(self.attributes) if is_not_empty(self, key) + key: to_dict_compatible(value=self.get_object_attribute(key)) + for key in sorted(self.attributes) + if is_not_empty(self, key) }

    Construct a dictionary out of non-null keys (from attributes property) present on this object

    +
    +def get_object_attribute(self, key: str) +
    +
    +
    + +Expand source code + +
    def get_object_attribute(self, key: str):
    +    return getattr(self, key, None)
    +
    +
    +
    def to_dict(self, *args) ‑> dict
    @@ -401,6 +454,7 @@

  • attributes
  • get_non_null_attributes
  • +
  • get_object_attribute
  • to_dict
  • validate_json
  • diff --git a/docs/reference/models/blocks/basic_components.html b/docs/reference/models/blocks/basic_components.html index 09ba7e190..2821b6a4b 100644 --- a/docs/reference/models/blocks/basic_components.html +++ b/docs/reference/models/blocks/basic_components.html @@ -1248,6 +1248,130 @@

    Inherited members

    +
    +class RawTextObject +(*, text: str) +
    +
    +
    + +Expand source code + +
    class RawTextObject(TextObject):
    +    """raw_text typed text object"""
    +
    +    type = "raw_text"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return {"text", "type"}
    +
    +    def __init__(self, *, text: str):
    +        """A raw text object used in table block cells.
    +        https://docs.slack.dev/reference/block-kit/composition-objects/text-object/
    +        https://docs.slack.dev/reference/block-kit/blocks/table-block
    +
    +        Args:
    +            text (required): The text content for the table block cell.
    +        """
    +        super().__init__(text=text, type=self.type)
    +
    +    @staticmethod
    +    def from_str(text: str) -> "RawTextObject":
    +        """Transforms a string into a RawTextObject"""
    +        return RawTextObject(text=text)
    +
    +    @staticmethod
    +    def direct_from_string(text: str) -> Dict[str, Any]:
    +        """Transforms a string into the required object shape to act as a RawTextObject"""
    +        return RawTextObject.from_str(text).to_dict()
    +
    +    @JsonValidator("text attribute must have at least 1 character")
    +    def _validate_text_min_length(self):
    +        return len(self.text) >= 1
    +
    +

    raw_text typed text object

    +

    A raw text object used in table block cells. +https://docs.slack.dev/reference/block-kit/composition-objects/text-object/ +https://docs.slack.dev/reference/block-kit/blocks/table-block

    +

    Args

    +
    +
    text : required
    +
    The text content for the table block cell.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Static methods

    +
    +
    +def direct_from_string(text: str) ‑> Dict[str, Any] +
    +
    +
    + +Expand source code + +
    @staticmethod
    +def direct_from_string(text: str) -> Dict[str, Any]:
    +    """Transforms a string into the required object shape to act as a RawTextObject"""
    +    return RawTextObject.from_str(text).to_dict()
    +
    +

    Transforms a string into the required object shape to act as a RawTextObject

    +
    +
    +def from_str(text: str) ‑> RawTextObject +
    +
    +
    + +Expand source code + +
    @staticmethod
    +def from_str(text: str) -> "RawTextObject":
    +    """Transforms a string into a RawTextObject"""
    +    return RawTextObject(text=text)
    +
    +

    Transforms a string into a RawTextObject

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return {"text", "type"}
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class SlackFile (*, id: str | None = None, url: str | None = None) @@ -1396,6 +1520,7 @@

    Subclasses

    Class variables

    @@ -1633,6 +1758,15 @@

  • +

    RawTextObject

    + +
  • +
  • SlackFile

    • attributes
    • diff --git a/docs/reference/models/blocks/blocks.html b/docs/reference/models/blocks/blocks.html index f266b5cad..722d12164 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -236,6 +236,8 @@

      Inherited members

      return VideoBlock(**block) elif type == RichTextBlock.type: return RichTextBlock(**block) + elif type == TableBlock.type: + return TableBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -269,6 +271,7 @@

      Subclasses

    • MarkdownBlock
    • RichTextBlock
    • SectionBlock
    • +
    • TableBlock
    • VideoBlock

    Class variables

    @@ -1580,6 +1583,123 @@

    Inherited members

  • +
    +class TableBlock +(*,
    rows: Sequence[Sequence[Dict[str, Any]]],
    column_settings: Sequence[Dict[str, Any] | None] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TableBlock(Block):
    +    type = "table"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union({"rows", "column_settings"})
    +
    +    def __init__(
    +        self,
    +        *,
    +        rows: Sequence[Sequence[Dict[str, Any]]],
    +        column_settings: Optional[Sequence[Optional[Dict[str, Any]]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays structured information in a table.
    +        https://docs.slack.dev/reference/block-kit/blocks/table-block
    +
    +        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.
    +            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.
    +                Any additional columns will have the default behavior. Maximum 20 items.
    +                See below for column settings schema.
    +            block_id: A unique identifier for a block. If not specified, a block_id will be generated.
    +                You can use this block_id when you receive an interaction payload to identify the source of the action.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.rows = rows
    +        self.column_settings = column_settings
    +
    +    @JsonValidator("rows attribute must be specified")
    +    def _validate_rows(self):
    +        return self.rows is not None and len(self.rows) > 0
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays structured information in a table. +https://docs.slack.dev/reference/block-kit/blocks/table-block

    +

    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.
    +
    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. +Any additional columns will have the default behavior. Maximum 20 items. +See below for column settings schema.
    +
    block_id
    +
    A unique identifier for a block. If not specified, a block_id will be generated. +You can use this block_id when you receive an interaction payload to identify the source of the action. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union({"rows", "column_settings"})
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class VideoBlock (*,
    block_id: str | None = None,
    alt_text: str | None = None,
    video_url: str | None = None,
    thumbnail_url: str | None = None,
    title: str | dict | PlainTextObject | None = None,
    title_url: str | None = None,
    description: str | dict | PlainTextObject | None = None,
    provider_icon_url: str | None = None,
    provider_name: str | None = None,
    author_name: str | None = None,
    **others: dict)
    @@ -1904,6 +2024,13 @@

    TableBlock

    + + +
  • VideoBlock

    • attributes
    • diff --git a/docs/reference/models/blocks/index.html b/docs/reference/models/blocks/index.html index 182ab927a..0d2047c12 100644 --- a/docs/reference/models/blocks/index.html +++ b/docs/reference/models/blocks/index.html @@ -258,6 +258,8 @@

      Inherited members

      return VideoBlock(**block) elif type == RichTextBlock.type: return RichTextBlock(**block) + elif type == TableBlock.type: + return TableBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -291,6 +293,7 @@

      Subclasses

    • MarkdownBlock
    • RichTextBlock
    • SectionBlock
    • +
    • TableBlock
    • VideoBlock

    Class variables

    @@ -5462,6 +5465,130 @@

    Inherited members

  • +
    +class RawTextObject +(*, text: str) +
    +
    +
    + +Expand source code + +
    class RawTextObject(TextObject):
    +    """raw_text typed text object"""
    +
    +    type = "raw_text"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return {"text", "type"}
    +
    +    def __init__(self, *, text: str):
    +        """A raw text object used in table block cells.
    +        https://docs.slack.dev/reference/block-kit/composition-objects/text-object/
    +        https://docs.slack.dev/reference/block-kit/blocks/table-block
    +
    +        Args:
    +            text (required): The text content for the table block cell.
    +        """
    +        super().__init__(text=text, type=self.type)
    +
    +    @staticmethod
    +    def from_str(text: str) -> "RawTextObject":
    +        """Transforms a string into a RawTextObject"""
    +        return RawTextObject(text=text)
    +
    +    @staticmethod
    +    def direct_from_string(text: str) -> Dict[str, Any]:
    +        """Transforms a string into the required object shape to act as a RawTextObject"""
    +        return RawTextObject.from_str(text).to_dict()
    +
    +    @JsonValidator("text attribute must have at least 1 character")
    +    def _validate_text_min_length(self):
    +        return len(self.text) >= 1
    +
    +

    raw_text typed text object

    +

    A raw text object used in table block cells. +https://docs.slack.dev/reference/block-kit/composition-objects/text-object/ +https://docs.slack.dev/reference/block-kit/blocks/table-block

    +

    Args

    +
    +
    text : required
    +
    The text content for the table block cell.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Static methods

    +
    +
    +def direct_from_string(text: str) ‑> Dict[str, Any] +
    +
    +
    + +Expand source code + +
    @staticmethod
    +def direct_from_string(text: str) -> Dict[str, Any]:
    +    """Transforms a string into the required object shape to act as a RawTextObject"""
    +    return RawTextObject.from_str(text).to_dict()
    +
    +

    Transforms a string into the required object shape to act as a RawTextObject

    +
    +
    +def from_str(text: str) ‑> RawTextObject +
    +
    +
    + +Expand source code + +
    @staticmethod
    +def from_str(text: str) -> "RawTextObject":
    +    """Transforms a string into a RawTextObject"""
    +    return RawTextObject(text=text)
    +
    +

    Transforms a string into a RawTextObject

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return {"text", "type"}
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class RichTextBlock (*,
    elements: Sequence[dict | RichTextElement],
    block_id: str | None = None,
    **others: dict)
    @@ -6952,6 +7079,123 @@

    Inherited members

    +
    +class TableBlock +(*,
    rows: Sequence[Sequence[Dict[str, Any]]],
    column_settings: Sequence[Dict[str, Any] | None] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TableBlock(Block):
    +    type = "table"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union({"rows", "column_settings"})
    +
    +    def __init__(
    +        self,
    +        *,
    +        rows: Sequence[Sequence[Dict[str, Any]]],
    +        column_settings: Optional[Sequence[Optional[Dict[str, Any]]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays structured information in a table.
    +        https://docs.slack.dev/reference/block-kit/blocks/table-block
    +
    +        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.
    +            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.
    +                Any additional columns will have the default behavior. Maximum 20 items.
    +                See below for column settings schema.
    +            block_id: A unique identifier for a block. If not specified, a block_id will be generated.
    +                You can use this block_id when you receive an interaction payload to identify the source of the action.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.rows = rows
    +        self.column_settings = column_settings
    +
    +    @JsonValidator("rows attribute must be specified")
    +    def _validate_rows(self):
    +        return self.rows is not None and len(self.rows) > 0
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays structured information in a table. +https://docs.slack.dev/reference/block-kit/blocks/table-block

    +

    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.
    +
    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. +Any additional columns will have the default behavior. Maximum 20 items. +See below for column settings schema.
    +
    block_id
    +
    A unique identifier for a block. If not specified, a block_id will be generated. +You can use this block_id when you receive an interaction payload to identify the source of the action. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union({"rows", "column_settings"})
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class TextObject (text: str,
    type: str | None = None,
    subtype: str | None = None,
    emoji: bool | None = None,
    **kwargs)
    @@ -7030,6 +7274,7 @@

    Subclasses

    Class variables

    @@ -8151,6 +8396,15 @@

    RawTextObject

    + + +
  • RichTextBlock

    • attributes
    • @@ -8248,6 +8502,13 @@

      TableBlock

      + + +
    • TextObject

      diff --git a/docs/reference/models/metadata/index.html b/docs/reference/models/metadata/index.html index 14663e790..1c4d7a69c 100644 --- a/docs/reference/models/metadata/index.html +++ b/docs/reference/models/metadata/index.html @@ -40,12 +40,2270 @@

      Module slack_sdk.models.metadata

      +

      Global variables

      +
      +
      var EntityType
      +
      +

      Custom field types

      +
      +

      Classes

      +
      +class ContentItemEntityFields +(preview: Dict[str, Any] | EntityImageField | None = None,
      description: Dict[str, Any] | EntityStringField | None = None,
      created_by: Dict[str, Any] | EntityTypedField | None = None,
      date_created: Dict[str, Any] | EntityTimestampField | None = None,
      date_updated: Dict[str, Any] | EntityTimestampField | None = None,
      last_modified_by: Dict[str, Any] | EntityTypedField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class ContentItemEntityFields(JsonObject):
      +    """Fields specific to content item entities"""
      +
      +    attributes = {
      +        "preview",
      +        "description",
      +        "created_by",
      +        "date_created",
      +        "date_updated",
      +        "last_modified_by",
      +    }
      +
      +    def __init__(
      +        self,
      +        preview: Optional[Union[Dict[str, Any], EntityImageField]] = None,
      +        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        **kwargs,
      +    ):
      +        self.preview = preview
      +        self.description = description
      +        self.created_by = created_by
      +        self.date_created = date_created
      +        self.date_updated = date_updated
      +        self.last_modified_by = last_modified_by
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Fields specific to content item entities

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityActionButton +(text: str,
      action_id: str,
      value: str | None = None,
      style: str | None = None,
      url: str | None = None,
      accessibility_label: str | None = None,
      processing_state: Dict[str, Any] | EntityActionProcessingState | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityActionButton(JsonObject):
      +    """Action button for entity"""
      +
      +    attributes = {
      +        "text",
      +        "action_id",
      +        "value",
      +        "style",
      +        "url",
      +        "accessibility_label",
      +        "processing_state",
      +    }
      +
      +    def __init__(
      +        self,
      +        text: str,
      +        action_id: str,
      +        value: Optional[str] = None,
      +        style: Optional[str] = None,
      +        url: Optional[str] = None,
      +        accessibility_label: Optional[str] = None,
      +        processing_state: Optional[Union[Dict[str, Any], EntityActionProcessingState]] = None,
      +        **kwargs,
      +    ):
      +        self.text = text
      +        self.action_id = action_id
      +        self.value = value
      +        self.style = style
      +        self.url = url
      +        self.accessibility_label = accessibility_label
      +        self.processing_state = processing_state
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Action button for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityActionProcessingState +(enabled: bool, interstitial_text: str | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityActionProcessingState(JsonObject):
      +    """Processing state configuration for entity action button"""
      +
      +    attributes = {
      +        "enabled",
      +        "interstitial_text",
      +    }
      +
      +    def __init__(
      +        self,
      +        enabled: bool,
      +        interstitial_text: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.enabled = enabled
      +        self.interstitial_text = interstitial_text
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Processing state configuration for entity action button

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityActions +(primary_actions: List[Dict[str, Any] | EntityActionButton] | None = None,
      overflow_actions: List[Dict[str, Any] | EntityActionButton] | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityActions(JsonObject):
      +    """Actions configuration for entity"""
      +
      +    attributes = {
      +        "primary_actions",
      +        "overflow_actions",
      +    }
      +
      +    def __init__(
      +        self,
      +        primary_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None,
      +        overflow_actions: Optional[List[Union[Dict[str, Any], EntityActionButton]]] = None,
      +        **kwargs,
      +    ):
      +        self.primary_actions = primary_actions
      +        self.overflow_actions = overflow_actions
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Actions configuration for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityArrayItemField +(type: str | None = None,
      label: str | None = None,
      value: str | int | None = None,
      link: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      long: bool | None = None,
      format: str | None = None,
      image_url: str | None = None,
      slack_file: Dict[str, Any] | None = None,
      alt_text: str | None = None,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      tag_color: str | None = None,
      user: Dict[str, Any] | EntityUserIDField | EntityUserField | None = None,
      entity_ref: Dict[str, Any] | EntityRefField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityArrayItemField(JsonObject):
      +    """Array item field for entity (similar to EntityTypedField but with optional type)"""
      +
      +    attributes = {
      +        "type",
      +        "label",
      +        "value",
      +        "link",
      +        "icon",
      +        "long",
      +        "format",
      +        "image_url",
      +        "slack_file",
      +        "alt_text",
      +        "edit",
      +        "tag_color",
      +        "user",
      +        "entity_ref",
      +    }
      +
      +    def __init__(
      +        self,
      +        type: Optional[str] = None,
      +        label: Optional[str] = None,
      +        value: Optional[Union[str, int]] = None,
      +        link: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        long: Optional[bool] = None,
      +        format: Optional[str] = None,
      +        image_url: Optional[str] = None,
      +        slack_file: Optional[Dict[str, Any]] = None,
      +        alt_text: Optional[str] = None,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        tag_color: Optional[str] = None,
      +        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
      +        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
      +        **kwargs,
      +    ):
      +        self.type = type
      +        self.label = label
      +        self.value = value
      +        self.link = link
      +        self.icon = icon
      +        self.long = long
      +        self.format = format
      +        self.image_url = image_url
      +        self.slack_file = slack_file
      +        self.alt_text = alt_text
      +        self.edit = edit
      +        self.tag_color = tag_color
      +        self.user = user
      +        self.entity_ref = entity_ref
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Array item field for entity (similar to EntityTypedField but with optional type)

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityAttributes +(title: Dict[str, Any] | EntityTitle,
      display_type: str | None = None,
      display_id: str | None = None,
      product_icon: Dict[str, Any] | EntityIconField | None = None,
      product_name: str | None = None,
      locale: str | None = None,
      full_size_preview: Dict[str, Any] | EntityFullSizePreview | None = None,
      metadata_last_modified: int | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityAttributes(JsonObject):
      +    """Attributes for an entity"""
      +
      +    attributes = {
      +        "title",
      +        "display_type",
      +        "display_id",
      +        "product_icon",
      +        "product_name",
      +        "locale",
      +        "full_size_preview",
      +        "metadata_last_modified",
      +    }
      +
      +    def __init__(
      +        self,
      +        title: Union[Dict[str, Any], EntityTitle],
      +        display_type: Optional[str] = None,
      +        display_id: Optional[str] = None,
      +        product_icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        product_name: Optional[str] = None,
      +        locale: Optional[str] = None,
      +        full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None,
      +        metadata_last_modified: Optional[int] = None,
      +        **kwargs,
      +    ):
      +        self.title = title
      +        self.display_type = display_type
      +        self.display_id = display_id
      +        self.product_icon = product_icon
      +        self.product_name = product_name
      +        self.locale = locale
      +        self.full_size_preview = full_size_preview
      +        self.metadata_last_modified = metadata_last_modified
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Attributes for an entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityBooleanCheckboxField +(type: str, text: str, description: str | None, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityBooleanCheckboxField(JsonObject):
      +    """Boolean checkbox properties"""
      +
      +    attributes = {"type", "text", "description"}
      +
      +    def __init__(
      +        self,
      +        type: str,
      +        text: str,
      +        description: Optional[str],
      +        **kwargs,
      +    ):
      +        self.type = type
      +        self.text = text
      +        self.description = description
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Boolean checkbox properties

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityBooleanTextField +(type: str,
      true_text: str,
      false_text: str,
      true_description: str | None,
      false_description: str | None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityBooleanTextField(JsonObject):
      +    """Boolean text properties"""
      +
      +    attributes = {"type", "true_text", "false_text", "true_description", "false_description"}
      +
      +    def __init__(
      +        self,
      +        type: str,
      +        true_text: str,
      +        false_text: str,
      +        true_description: Optional[str],
      +        false_description: Optional[str],
      +        **kwargs,
      +    ):
      +        self.type = type
      +        self.true_text = (true_text,)
      +        self.false_text = (false_text,)
      +        self.true_description = (true_description,)
      +        self.false_description = (false_description,)
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Boolean text properties

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityCustomField +(label: str,
      key: str,
      type: str,
      value: str | int | List[Dict[str, Any] | EntityArrayItemField] | None = None,
      link: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      long: bool | None = None,
      format: str | None = None,
      image_url: str | None = None,
      slack_file: Dict[str, Any] | None = None,
      alt_text: str | None = None,
      tag_color: str | None = None,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      item_type: str | None = None,
      user: Dict[str, Any] | EntityUserIDField | EntityUserField | None = None,
      entity_ref: Dict[str, Any] | EntityRefField | None = None,
      boolean: Dict[str, Any] | EntityBooleanCheckboxField | EntityBooleanTextField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityCustomField(JsonObject):
      +    """Custom field for entity with flexible types"""
      +
      +    attributes = {
      +        "label",
      +        "key",
      +        "type",
      +        "value",
      +        "link",
      +        "icon",
      +        "long",
      +        "format",
      +        "image_url",
      +        "slack_file",
      +        "alt_text",
      +        "tag_color",
      +        "edit",
      +        "item_type",
      +        "user",
      +        "entity_ref",
      +        "boolean",
      +    }
      +
      +    def __init__(
      +        self,
      +        label: str,
      +        key: str,
      +        type: str,
      +        value: Optional[Union[str, int, List[Union[Dict[str, Any], EntityArrayItemField]]]] = None,
      +        link: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        long: Optional[bool] = None,
      +        format: Optional[str] = None,
      +        image_url: Optional[str] = None,
      +        slack_file: Optional[Dict[str, Any]] = None,
      +        alt_text: Optional[str] = None,
      +        tag_color: Optional[str] = None,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        item_type: Optional[str] = None,
      +        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
      +        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
      +        boolean: Optional[Union[Dict[str, Any], EntityBooleanCheckboxField, EntityBooleanTextField]] = None,
      +        **kwargs,
      +    ):
      +        self.label = label
      +        self.key = key
      +        self.type = type
      +        self.value = value
      +        self.link = link
      +        self.icon = icon
      +        self.long = long
      +        self.format = format
      +        self.image_url = image_url
      +        self.slack_file = slack_file
      +        self.alt_text = alt_text
      +        self.tag_color = tag_color
      +        self.edit = edit
      +        self.item_type = item_type
      +        self.user = user
      +        self.entity_ref = entity_ref
      +        self.boolean = boolean
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +    @EnumValidator("type", CustomFieldType)
      +    def type_valid(self):
      +        return self.type is None or self.type in CustomFieldType
      +
      +

      Custom field for entity with flexible types

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Methods

      +
      +
      +def type_valid(self) +
      +
      +
      + +Expand source code + +
      @EnumValidator("type", CustomFieldType)
      +def type_valid(self):
      +    return self.type is None or self.type in CustomFieldType
      +
      +
      +
      +
      +

      Inherited members

      + +
      +
      +class EntityEditNumberConfig +(is_decimal_allowed: bool | None = None,
      min_value: int | float | None = None,
      max_value: int | float | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityEditNumberConfig(JsonObject):
      +    """Number configuration for entity edit support"""
      +
      +    attributes = {
      +        "is_decimal_allowed",
      +        "min_value",
      +        "max_value",
      +    }
      +
      +    def __init__(
      +        self,
      +        is_decimal_allowed: Optional[bool] = None,
      +        min_value: Optional[Union[int, float]] = None,
      +        max_value: Optional[Union[int, float]] = None,
      +        **kwargs,
      +    ):
      +        self.is_decimal_allowed = is_decimal_allowed
      +        self.min_value = min_value
      +        self.max_value = max_value
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Number configuration for entity edit support

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityEditSelectConfig +(current_value: str | None = None,
      current_values: List[str] | None = None,
      static_options: List[Dict[str, Any]] | None = None,
      fetch_options_dynamically: bool | None = None,
      min_query_length: int | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityEditSelectConfig(JsonObject):
      +    """Select configuration for entity edit support"""
      +
      +    attributes = {
      +        "current_value",
      +        "current_values",
      +        "static_options",
      +        "fetch_options_dynamically",
      +        "min_query_length",
      +    }
      +
      +    def __init__(
      +        self,
      +        current_value: Optional[str] = None,
      +        current_values: Optional[List[str]] = None,
      +        static_options: Optional[List[Dict[str, Any]]] = None,  # Option[]
      +        fetch_options_dynamically: Optional[bool] = None,
      +        min_query_length: Optional[int] = None,
      +        **kwargs,
      +    ):
      +        self.current_value = current_value
      +        self.current_values = current_values
      +        self.static_options = static_options
      +        self.fetch_options_dynamically = fetch_options_dynamically
      +        self.min_query_length = min_query_length
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Select configuration for entity edit support

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityEditSupport +(enabled: bool,
      placeholder: Dict[str, Any] | None = None,
      hint: Dict[str, Any] | None = None,
      optional: bool | None = None,
      select: Dict[str, Any] | EntityEditSelectConfig | None = None,
      number: Dict[str, Any] | EntityEditNumberConfig | None = None,
      text: Dict[str, Any] | EntityEditTextConfig | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityEditSupport(JsonObject):
      +    """Edit support configuration for entity fields"""
      +
      +    attributes = {
      +        "enabled",
      +        "placeholder",
      +        "hint",
      +        "optional",
      +        "select",
      +        "number",
      +        "text",
      +    }
      +
      +    def __init__(
      +        self,
      +        enabled: bool,
      +        placeholder: Optional[Dict[str, Any]] = None,  # PlainTextElement
      +        hint: Optional[Dict[str, Any]] = None,  # PlainTextElement
      +        optional: Optional[bool] = None,
      +        select: Optional[Union[Dict[str, Any], EntityEditSelectConfig]] = None,
      +        number: Optional[Union[Dict[str, Any], EntityEditNumberConfig]] = None,
      +        text: Optional[Union[Dict[str, Any], EntityEditTextConfig]] = None,
      +        **kwargs,
      +    ):
      +        self.enabled = enabled
      +        self.placeholder = placeholder
      +        self.hint = hint
      +        self.optional = optional
      +        self.select = select
      +        self.number = number
      +        self.text = text
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Edit support configuration for entity fields

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityEditTextConfig +(min_length: int | None = None, max_length: int | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityEditTextConfig(JsonObject):
      +    """Text configuration for entity edit support"""
      +
      +    attributes = {
      +        "min_length",
      +        "max_length",
      +    }
      +
      +    def __init__(
      +        self,
      +        min_length: Optional[int] = None,
      +        max_length: Optional[int] = None,
      +        **kwargs,
      +    ):
      +        self.min_length = min_length
      +        self.max_length = max_length
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Text configuration for entity edit support

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityFullSizePreview +(is_supported: bool,
      preview_url: str | None = None,
      mime_type: str | None = None,
      error: Dict[str, Any] | EntityFullSizePreviewError | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityFullSizePreview(JsonObject):
      +    """Full-size preview configuration for entity"""
      +
      +    attributes = {
      +        "is_supported",
      +        "preview_url",
      +        "mime_type",
      +        "error",
      +    }
      +
      +    def __init__(
      +        self,
      +        is_supported: bool,
      +        preview_url: Optional[str] = None,
      +        mime_type: Optional[str] = None,
      +        error: Optional[Union[Dict[str, Any], EntityFullSizePreviewError]] = None,
      +        **kwargs,
      +    ):
      +        self.is_supported = is_supported
      +        self.preview_url = preview_url
      +        self.mime_type = mime_type
      +        self.error = error
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Full-size preview configuration for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityFullSizePreviewError +(code: str, message: str | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityFullSizePreviewError(JsonObject):
      +    """Error information for full-size preview"""
      +
      +    attributes = {
      +        "code",
      +        "message",
      +    }
      +
      +    def __init__(
      +        self,
      +        code: str,
      +        message: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.code = code
      +        self.message = message
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Error information for full-size preview

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityIconField +(alt_text: str,
      url: str | None = None,
      slack_file: Dict[str, Any] | EntityIconSlackFile | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityIconField(JsonObject):
      +    """Icon field for entity attributes"""
      +
      +    attributes = {
      +        "alt_text",
      +        "url",
      +        "slack_file",
      +    }
      +
      +    def __init__(
      +        self,
      +        alt_text: str,
      +        url: Optional[str] = None,
      +        slack_file: Optional[Union[Dict[str, Any], EntityIconSlackFile]] = None,
      +        **kwargs,
      +    ):
      +        self.alt_text = alt_text
      +        self.url = url
      +        self.slack_file = slack_file
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Icon field for entity attributes

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityIconSlackFile +(id: str | None = None, url: str | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityIconSlackFile(JsonObject):
      +    """Slack file reference for entity icon"""
      +
      +    attributes = {
      +        "id",
      +        "url",
      +    }
      +
      +    def __init__(
      +        self,
      +        id: Optional[str] = None,
      +        url: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.id = id
      +        self.url = url
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Slack file reference for entity icon

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityImageField +(alt_text: str,
      label: str | None = None,
      image_url: str | None = None,
      slack_file: Dict[str, Any] | None = None,
      title: str | None = None,
      type: str | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityImageField(JsonObject):
      +    """Image field for entity"""
      +
      +    attributes = {
      +        "alt_text",
      +        "label",
      +        "image_url",
      +        "slack_file",
      +        "title",
      +        "type",
      +    }
      +
      +    def __init__(
      +        self,
      +        alt_text: str,
      +        label: Optional[str] = None,
      +        image_url: Optional[str] = None,
      +        slack_file: Optional[Dict[str, Any]] = None,
      +        title: Optional[str] = None,
      +        type: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.alt_text = alt_text
      +        self.label = label
      +        self.image_url = image_url
      +        self.slack_file = slack_file
      +        self.title = title
      +        self.type = type
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Image field for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityMetadata +(entity_type: str,
      entity_payload: Dict[str, Any] | EntityPayload,
      external_ref: Dict[str, Any] | ExternalRef,
      url: str,
      app_unfurl_url: str | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityMetadata(JsonObject):
      +    """Work object entity metadata
      +
      +    https://docs.slack.dev/messaging/work-objects/
      +    """
      +
      +    attributes = {
      +        "entity_type",
      +        "entity_payload",
      +        "external_ref",
      +        "url",
      +        "app_unfurl_url",
      +    }
      +
      +    def __init__(
      +        self,
      +        entity_type: str,
      +        entity_payload: Union[Dict[str, Any], EntityPayload],
      +        external_ref: Union[Dict[str, Any], ExternalRef],
      +        url: str,
      +        app_unfurl_url: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.entity_type = entity_type
      +        self.entity_payload = entity_payload
      +        self.external_ref = external_ref
      +        self.url = url
      +        self.app_unfurl_url = app_unfurl_url
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +    @EnumValidator("entity_type", EntityType)
      +    def entity_type_valid(self):
      +        return self.entity_type is None or self.entity_type in EntityType
      +
      + +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Methods

      +
      +
      +def entity_type_valid(self) +
      +
      +
      + +Expand source code + +
      @EnumValidator("entity_type", EntityType)
      +def entity_type_valid(self):
      +    return self.entity_type is None or self.entity_type in EntityType
      +
      +
      +
      +
      +

      Inherited members

      + +
      +
      +class EntityPayload +(attributes: Dict[str, Any] | EntityAttributes,
      fields: Dict[str, Any] | ContentItemEntityFields | FileEntityFields | IncidentEntityFields | TaskEntityFields | None = None,
      custom_fields: List[Dict[str, Any] | EntityCustomField] | None = None,
      slack_file: Dict[str, Any] | FileEntitySlackFile | None = None,
      display_order: List[str] | None = None,
      actions: Dict[str, Any] | EntityActions | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityPayload(JsonObject):
      +    """Payload schema for an entity"""
      +
      +    attributes = {
      +        "attributes",
      +        "fields",
      +        "custom_fields",
      +        "slack_file",
      +        "display_order",
      +        "actions",
      +    }
      +
      +    def __init__(
      +        self,
      +        attributes: Union[Dict[str, Any], EntityAttributes],
      +        fields: Optional[
      +            Union[Dict[str, Any], ContentItemEntityFields, FileEntityFields, IncidentEntityFields, TaskEntityFields]
      +        ] = None,
      +        custom_fields: Optional[List[Union[Dict[str, Any], EntityCustomField]]] = None,
      +        slack_file: Optional[Union[Dict[str, Any], FileEntitySlackFile]] = None,
      +        display_order: Optional[List[str]] = None,
      +        actions: Optional[Union[Dict[str, Any], EntityActions]] = None,
      +        **kwargs,
      +    ):
      +        # Store entity attributes data with a different internal name to avoid
      +        # shadowing the class-level 'attributes' set used for JSON serialization
      +        self._entity_attributes = attributes
      +        self.fields = fields
      +        self.custom_fields = custom_fields
      +        self.slack_file = slack_file
      +        self.display_order = display_order
      +        self.actions = actions
      +        self.additional_attributes = kwargs
      +
      +    @property
      +    def entity_attributes(self) -> Union[Dict[str, Any], EntityAttributes]:
      +        """Get the entity attributes data.
      +
      +        Note: Use this property to access the attributes data. The class-level
      +        'attributes' is reserved for the JSON serialization schema.
      +        """
      +        return self._entity_attributes
      +
      +    @entity_attributes.setter
      +    def entity_attributes(self, value: Union[Dict[str, Any], EntityAttributes]):
      +        """Set the entity attributes data."""
      +        self._entity_attributes = value
      +
      +    def get_object_attribute(self, key: str):
      +        if key == "attributes":
      +            return self._entity_attributes
      +        else:
      +            return getattr(self, key, None)
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Payload schema for an entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Instance variables

      +
      +
      prop entity_attributes : Dict[str, Any] | EntityAttributes
      +
      +
      + +Expand source code + +
      @property
      +def entity_attributes(self) -> Union[Dict[str, Any], EntityAttributes]:
      +    """Get the entity attributes data.
      +
      +    Note: Use this property to access the attributes data. The class-level
      +    'attributes' is reserved for the JSON serialization schema.
      +    """
      +    return self._entity_attributes
      +
      +

      Get the entity attributes data.

      +

      Note: Use this property to access the attributes data. The class-level +'attributes' is reserved for the JSON serialization schema.

      +
      +
      +

      Methods

      +
      +
      +def get_object_attribute(self, key: str) +
      +
      +
      + +Expand source code + +
      def get_object_attribute(self, key: str):
      +    if key == "attributes":
      +        return self._entity_attributes
      +    else:
      +        return getattr(self, key, None)
      +
      +
      +
      +
      +

      Inherited members

      + +
      +
      +class EntityRefField +(entity_url: str,
      external_ref: Dict[str, Any] | ExternalRef,
      title: str,
      display_type: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityRefField(JsonObject):
      +    """Entity reference field"""
      +
      +    attributes = {
      +        "entity_url",
      +        "external_ref",
      +        "title",
      +        "display_type",
      +        "icon",
      +    }
      +
      +    def __init__(
      +        self,
      +        entity_url: str,
      +        external_ref: Union[Dict[str, Any], ExternalRef],
      +        title: str,
      +        display_type: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        **kwargs,
      +    ):
      +        self.entity_url = entity_url
      +        self.external_ref = external_ref
      +        self.title = title
      +        self.display_type = display_type
      +        self.icon = icon
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Entity reference field

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityStringField +(value: str,
      label: str | None = None,
      format: str | None = None,
      link: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      long: bool | None = None,
      type: str | None = None,
      tag_color: str | None = None,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityStringField(JsonObject):
      +    """String field for entity"""
      +
      +    attributes = {
      +        "value",
      +        "label",
      +        "format",
      +        "link",
      +        "icon",
      +        "long",
      +        "type",
      +        "tag_color",
      +        "edit",
      +    }
      +
      +    def __init__(
      +        self,
      +        value: str,
      +        label: Optional[str] = None,
      +        format: Optional[str] = None,
      +        link: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        long: Optional[bool] = None,
      +        type: Optional[str] = None,
      +        tag_color: Optional[str] = None,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        **kwargs,
      +    ):
      +        self.value = value
      +        self.label = label
      +        self.format = format
      +        self.link = link
      +        self.icon = icon
      +        self.long = long
      +        self.type = type
      +        self.tag_color = tag_color
      +        self.edit = edit
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      String field for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityTimestampField +(value: int,
      label: str | None = None,
      type: str | None = None,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityTimestampField(JsonObject):
      +    """Timestamp field for entity"""
      +
      +    attributes = {
      +        "value",
      +        "label",
      +        "type",
      +        "edit",
      +    }
      +
      +    def __init__(
      +        self,
      +        value: int,
      +        label: Optional[str] = None,
      +        type: Optional[str] = None,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        **kwargs,
      +    ):
      +        self.value = value
      +        self.label = label
      +        self.type = type
      +        self.edit = edit
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Timestamp field for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityTitle +(text: str,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityTitle(JsonObject):
      +    """Title for entity attributes"""
      +
      +    attributes = {
      +        "text",
      +        "edit",
      +    }
      +
      +    def __init__(
      +        self,
      +        text: str,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        **kwargs,
      +    ):
      +        self.text = text
      +        self.edit = edit
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Title for entity attributes

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityTypedField +(type: str,
      label: str | None = None,
      value: str | int | None = None,
      link: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      long: bool | None = None,
      format: str | None = None,
      image_url: str | None = None,
      slack_file: Dict[str, Any] | None = None,
      alt_text: str | None = None,
      edit: Dict[str, Any] | EntityEditSupport | None = None,
      tag_color: str | None = None,
      user: Dict[str, Any] | EntityUserIDField | EntityUserField | None = None,
      entity_ref: Dict[str, Any] | EntityRefField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityTypedField(JsonObject):
      +    """Typed field for entity with various display options"""
      +
      +    attributes = {
      +        "type",
      +        "label",
      +        "value",
      +        "link",
      +        "icon",
      +        "long",
      +        "format",
      +        "image_url",
      +        "slack_file",
      +        "alt_text",
      +        "edit",
      +        "tag_color",
      +        "user",
      +        "entity_ref",
      +    }
      +
      +    def __init__(
      +        self,
      +        type: str,
      +        label: Optional[str] = None,
      +        value: Optional[Union[str, int]] = None,
      +        link: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        long: Optional[bool] = None,
      +        format: Optional[str] = None,
      +        image_url: Optional[str] = None,
      +        slack_file: Optional[Dict[str, Any]] = None,
      +        alt_text: Optional[str] = None,
      +        edit: Optional[Union[Dict[str, Any], EntityEditSupport]] = None,
      +        tag_color: Optional[str] = None,
      +        user: Optional[Union[Dict[str, Any], EntityUserIDField, EntityUserField]] = None,
      +        entity_ref: Optional[Union[Dict[str, Any], EntityRefField]] = None,
      +        **kwargs,
      +    ):
      +        self.type = type
      +        self.label = label
      +        self.value = value
      +        self.link = link
      +        self.icon = icon
      +        self.long = long
      +        self.format = format
      +        self.image_url = image_url
      +        self.slack_file = slack_file
      +        self.alt_text = alt_text
      +        self.edit = edit
      +        self.tag_color = tag_color
      +        self.user = user
      +        self.entity_ref = entity_ref
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Typed field for entity with various display options

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityUserField +(text: str,
      url: str | None = None,
      email: str | None = None,
      icon: Dict[str, Any] | EntityIconField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EntityUserField(JsonObject):
      +    """User field for entity"""
      +
      +    attributes = {
      +        "text",
      +        "url",
      +        "email",
      +        "icon",
      +    }
      +
      +    def __init__(
      +        self,
      +        text: str,
      +        url: Optional[str] = None,
      +        email: Optional[str] = None,
      +        icon: Optional[Union[Dict[str, Any], EntityIconField]] = None,
      +        **kwargs,
      +    ):
      +        self.text = text
      +        self.url = url
      +        self.email = email
      +        self.icon = icon
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      User field for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EntityUserIDField +(user_id: str, **kwargs) +
      +
      +
      + +Expand source code + +
      class EntityUserIDField(JsonObject):
      +    """User ID field for entity"""
      +
      +    attributes = {
      +        "user_id",
      +    }
      +
      +    def __init__(
      +        self,
      +        user_id: str,
      +        **kwargs,
      +    ):
      +        self.user_id = user_id
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      User ID field for entity

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class EventAndEntityMetadata +(event_type: str | None = None,
      event_payload: Dict[str, Any] | None = None,
      entities: List[Dict[str, Any] | EntityMetadata] | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class EventAndEntityMetadata(JsonObject):
      +    """Message metadata with entities
      +
      +    https://docs.slack.dev/messaging/message-metadata/
      +    https://docs.slack.dev/messaging/work-objects/
      +    """
      +
      +    attributes = {"event_type", "event_payload", "entities"}
      +
      +    def __init__(
      +        self,
      +        event_type: Optional[str] = None,
      +        event_payload: Optional[Dict[str, Any]] = None,
      +        entities: Optional[List[Union[Dict[str, Any], EntityMetadata]]] = None,
      +        **kwargs,
      +    ):
      +        self.event_type = event_type
      +        self.event_payload = event_payload
      +        self.entities = entities
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      + +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class ExternalRef +(id: str, type: str | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class ExternalRef(JsonObject):
      +    """Reference (and optional type) used to identify an entity within the developer's system"""
      +
      +    attributes = {
      +        "id",
      +        "type",
      +    }
      +
      +    def __init__(
      +        self,
      +        id: str,
      +        type: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.id = id
      +        self.type = type
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Reference (and optional type) used to identify an entity within the developer's system

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class FileEntityFields +(preview: Dict[str, Any] | EntityImageField | None = None,
      created_by: Dict[str, Any] | EntityTypedField | None = None,
      date_created: Dict[str, Any] | EntityTimestampField | None = None,
      date_updated: Dict[str, Any] | EntityTimestampField | None = None,
      last_modified_by: Dict[str, Any] | EntityTypedField | None = None,
      file_size: Dict[str, Any] | EntityStringField | None = None,
      mime_type: Dict[str, Any] | EntityStringField | None = None,
      full_size_preview: Dict[str, Any] | EntityFullSizePreview | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class FileEntityFields(JsonObject):
      +    """Fields specific to file entities"""
      +
      +    attributes = {
      +        "preview",
      +        "created_by",
      +        "date_created",
      +        "date_updated",
      +        "last_modified_by",
      +        "file_size",
      +        "mime_type",
      +        "full_size_preview",
      +    }
      +
      +    def __init__(
      +        self,
      +        preview: Optional[Union[Dict[str, Any], EntityImageField]] = None,
      +        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        last_modified_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        file_size: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        mime_type: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        full_size_preview: Optional[Union[Dict[str, Any], EntityFullSizePreview]] = None,
      +        **kwargs,
      +    ):
      +        self.preview = preview
      +        self.created_by = created_by
      +        self.date_created = date_created
      +        self.date_updated = date_updated
      +        self.last_modified_by = last_modified_by
      +        self.file_size = file_size
      +        self.mime_type = mime_type
      +        self.full_size_preview = full_size_preview
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Fields specific to file entities

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class FileEntitySlackFile +(id: str, type: str | None = None, **kwargs) +
      +
      +
      + +Expand source code + +
      class FileEntitySlackFile(JsonObject):
      +    """Slack file reference for file entities"""
      +
      +    attributes = {
      +        "id",
      +        "type",
      +    }
      +
      +    def __init__(
      +        self,
      +        id: str,
      +        type: Optional[str] = None,
      +        **kwargs,
      +    ):
      +        self.id = id
      +        self.type = type
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Slack file reference for file entities

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      +
      +class IncidentEntityFields +(status: Dict[str, Any] | EntityStringField | None = None,
      priority: Dict[str, Any] | EntityStringField | None = None,
      urgency: Dict[str, Any] | EntityStringField | None = None,
      created_by: Dict[str, Any] | EntityTypedField | None = None,
      assigned_to: Dict[str, Any] | EntityTypedField | None = None,
      date_created: Dict[str, Any] | EntityTimestampField | None = None,
      date_updated: Dict[str, Any] | EntityTimestampField | None = None,
      description: Dict[str, Any] | EntityStringField | None = None,
      service: Dict[str, Any] | EntityStringField | None = None,
      **kwargs)
      +
      +
      +
      + +Expand source code + +
      class IncidentEntityFields(JsonObject):
      +    """Fields specific to incident entities"""
      +
      +    attributes = {
      +        "status",
      +        "priority",
      +        "urgency",
      +        "created_by",
      +        "assigned_to",
      +        "date_created",
      +        "date_updated",
      +        "description",
      +        "service",
      +    }
      +
      +    def __init__(
      +        self,
      +        status: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        priority: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        urgency: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        assigned_to: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
      +        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
      +        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        service: Optional[Union[Dict[str, Any], EntityStringField]] = None,
      +        **kwargs,
      +    ):
      +        self.status = status
      +        self.priority = priority
      +        self.urgency = urgency
      +        self.created_by = created_by
      +        self.assigned_to = assigned_to
      +        self.date_created = date_created
      +        self.date_updated = date_updated
      +        self.description = description
      +        self.service = service
      +        self.additional_attributes = kwargs
      +
      +    def __str__(self):
      +        return str(self.get_non_null_attributes())
      +
      +    def __repr__(self):
      +        return self.__str__()
      +
      +

      Fields specific to incident entities

      +

      Ancestors

      + +

      Class variables

      +
      +
      var attributes
      +
      +

      The type of the None singleton.

      +
      +
      +

      Inherited members

      + +
      class Metadata (event_type: str, event_payload: Dict[str, Any], **kwargs) @@ -107,6 +2365,81 @@

      Inherited members

    +
    +class TaskEntityFields +(description: Dict[str, Any] | EntityStringField | None = None,
    created_by: Dict[str, Any] | EntityTypedField | None = None,
    date_created: Dict[str, Any] | EntityTimestampField | None = None,
    date_updated: Dict[str, Any] | EntityTimestampField | None = None,
    assignee: Dict[str, Any] | EntityTypedField | None = None,
    status: Dict[str, Any] | EntityStringField | None = None,
    due_date: Dict[str, Any] | EntityTypedField | None = None,
    priority: Dict[str, Any] | EntityStringField | None = None,
    **kwargs)
    +
    +
    +
    + +Expand source code + +
    class TaskEntityFields(JsonObject):
    +    """Fields specific to task entities"""
    +
    +    attributes = {
    +        "description",
    +        "created_by",
    +        "date_created",
    +        "date_updated",
    +        "assignee",
    +        "status",
    +        "due_date",
    +        "priority",
    +    }
    +
    +    def __init__(
    +        self,
    +        description: Optional[Union[Dict[str, Any], EntityStringField]] = None,
    +        created_by: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
    +        date_created: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
    +        date_updated: Optional[Union[Dict[str, Any], EntityTimestampField]] = None,
    +        assignee: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
    +        status: Optional[Union[Dict[str, Any], EntityStringField]] = None,
    +        due_date: Optional[Union[Dict[str, Any], EntityTypedField]] = None,
    +        priority: Optional[Union[Dict[str, Any], EntityStringField]] = None,
    +        **kwargs,
    +    ):
    +        self.description = description
    +        self.created_by = created_by
    +        self.date_created = date_created
    +        self.date_updated = date_updated
    +        self.assignee = assignee
    +        self.status = status
    +        self.due_date = due_date
    +        self.priority = priority
    +        self.additional_attributes = kwargs
    +
    +    def __str__(self):
    +        return str(self.get_non_null_attributes())
    +
    +    def __repr__(self):
    +        return self.__str__()
    +
    +

    Fields specific to task entities

    +

    Ancestors

    + +

    Class variables

    +
    +
    var attributes
    +
    +

    The type of the None singleton.

    +
    +
    +

    Inherited members

    + +
  • @@ -120,14 +2453,221 @@

    Inherited members

  • slack_sdk.models
  • +
  • Global variables

    + +
  • Classes

  • diff --git a/docs/reference/web/async_client.html b/docs/reference/web/async_client.html index 6e9c712fe..2ec1ba84a 100644 --- a/docs/reference/web/async_client.html +++ b/docs/reference/web/async_client.html @@ -2790,7 +2790,7 @@

    Classes

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> AsyncSlackResponse: @@ -3019,6 +3019,7 @@

    Classes

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3035,6 +3036,7 @@

    Classes

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3666,6 +3668,30 @@

    Classes

    kwargs.update({"include_categories": include_categories}) return await self.api_call("emoji.list", http_verb="GET", params=kwargs) + async def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[Union[Dict, EntityMetadata]] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) + return await self.api_call("entity.presentDetails", json=kwargs) + async def files_comments_delete( self, *, @@ -4920,6 +4946,249 @@

    Classes

    ) return await self.api_call("search.messages", http_verb="GET", params=kwargs) + async def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.access.delete", json=kwargs) + + async def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.access.set", json=kwargs) + + async def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.create", json=kwargs) + + async def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> AsyncSlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.download.get", json=kwargs) + + async def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.download.start", json=kwargs) + + async def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.create", json=kwargs) + + async def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> AsyncSlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.delete", json=kwargs) + + async def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> AsyncSlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + async def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.info", json=kwargs) + + async def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.list", json=kwargs) + + async def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> AsyncSlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.items.update", json=kwargs) + + async def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("slackLists.update", json=kwargs) + async def stars_add( self, *, @@ -10066,7 +10335,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.postEphemeral

    -async def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +async def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | EventAndEntityMetadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    @@ -10092,7 +10361,7 @@

    Methods

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> AsyncSlackResponse: @@ -10414,7 +10683,7 @@

    Example

    -async def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +async def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    metadata: Dict | EventAndEntityMetadata | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    @@ -10429,6 +10698,7 @@

    Example

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -10445,6 +10715,7 @@

    Example

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -11476,6 +11747,41 @@

    Example

    +
    +async def entity_presentDetails(self,
    trigger_id: str,
    metadata: Dict | EntityMetadata | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    error: Dict[str, Any] | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def entity_presentDetails(
    +    self,
    +    trigger_id: str,
    +    metadata: Optional[Union[Dict, EntityMetadata]] = None,
    +    user_auth_required: Optional[bool] = None,
    +    user_auth_url: Optional[str] = None,
    +    error: Optional[Dict[str, Any]] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Provides entity details for the flexpane.
    +    https://docs.slack.dev/reference/methods/entity.presentDetails/
    +    """
    +    kwargs.update({"trigger_id": trigger_id})
    +    if metadata is not None:
    +        kwargs.update({"metadata": metadata})
    +    if user_auth_required is not None:
    +        kwargs.update({"user_auth_required": user_auth_required})
    +    if user_auth_url is not None:
    +        kwargs.update({"user_auth_url": user_auth_url})
    +    if error is not None:
    +        kwargs.update({"error": error})
    +    _parse_web_class_objects(kwargs)
    +    return await self.api_call("entity.presentDetails", json=kwargs)
    +
    +

    Provides entity details for the flexpane. +https://docs.slack.dev/reference/methods/entity.presentDetails/

    +
    async def files_comments_delete(self, *, file: str, id: str, **kwargs) ‑> AsyncSlackResponse
    @@ -13451,6 +13757,381 @@

    Example

    Searches for messages matching a query. https://docs.slack.dev/reference/methods/search.messages

    +
    +async def slackLists_access_delete(self,
    *,
    list_id: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_access_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Revoke access to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.delete
    +    """
    +    kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.access.delete", json=kwargs)
    +
    +

    Revoke access to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.delete

    +
    +
    +async def slackLists_access_set(self,
    *,
    list_id: str,
    access_level: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_access_set(
    +    self,
    +    *,
    +    list_id: str,
    +    access_level: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Set the access level to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.set
    +    """
    +    kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.access.set", json=kwargs)
    +
    +

    Set the access level to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.set

    +
    +
    +async def slackLists_create(self,
    *,
    name: str,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    schema: List[Dict[str, Any]] | None = None,
    copy_from_list_id: str | None = None,
    include_copied_list_records: bool | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_create(
    +    self,
    +    *,
    +    name: str,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    schema: Optional[List[Dict[str, Any]]] = None,
    +    copy_from_list_id: Optional[str] = None,
    +    include_copied_list_records: Optional[bool] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Creates a List.
    +    https://docs.slack.dev/reference/methods/slackLists.create
    +    """
    +    kwargs.update(
    +        {
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "schema": schema,
    +            "copy_from_list_id": copy_from_list_id,
    +            "include_copied_list_records": include_copied_list_records,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.create", json=kwargs)
    +
    + +
    +
    +async def slackLists_download_get(self, *, list_id: str, job_id: str, **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_download_get(
    +    self,
    +    *,
    +    list_id: str,
    +    job_id: str,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Retrieve List download URL from an export job to download List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.get
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "job_id": job_id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.download.get", json=kwargs)
    +
    +

    Retrieve List download URL from an export job to download List contents. +https://docs.slack.dev/reference/methods/slackLists.download.get

    +
    +
    +async def slackLists_download_start(self, *, list_id: str, include_archived: bool | None = None, **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_download_start(
    +    self,
    +    *,
    +    list_id: str,
    +    include_archived: Optional[bool] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Initiate a job to export List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.start
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "include_archived": include_archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.download.start", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_create(self,
    *,
    list_id: str,
    duplicated_item_id: str | None = None,
    parent_item_id: str | None = None,
    initial_fields: List[Dict[str, Any]] | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_items_create(
    +    self,
    +    *,
    +    list_id: str,
    +    duplicated_item_id: Optional[str] = None,
    +    parent_item_id: Optional[str] = None,
    +    initial_fields: Optional[List[Dict[str, Any]]] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Add a new item to an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.create
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "duplicated_item_id": duplicated_item_id,
    +            "parent_item_id": parent_item_id,
    +            "initial_fields": initial_fields,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.create", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_delete(self, *, list_id: str, id: str, **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_items_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Deletes an item from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.delete
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.delete", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_deleteMultiple(self, *, list_id: str, ids: List[str], **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_items_deleteMultiple(
    +    self,
    +    *,
    +    list_id: str,
    +    ids: List[str],
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Deletes multiple items from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "ids": ids,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.deleteMultiple", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_info(self, *, list_id: str, id: str, include_is_subscribed: bool | None = None, **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_items_info(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    include_is_subscribed: Optional[bool] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Get a row from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.info
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +            "include_is_subscribed": include_is_subscribed,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.info", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_list(self,
    *,
    list_id: str,
    limit: int | None = None,
    cursor: str | None = None,
    archived: bool | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_items_list(
    +    self,
    +    *,
    +    list_id: str,
    +    limit: Optional[int] = None,
    +    cursor: Optional[str] = None,
    +    archived: Optional[bool] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Get records from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.list
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "limit": limit,
    +            "cursor": cursor,
    +            "archived": archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.list", json=kwargs)
    +
    + +
    +
    +async def slackLists_items_update(self, *, list_id: str, cells: List[Dict[str, Any]], **kwargs) ‑> AsyncSlackResponse +
    +
    +
    + +Expand source code + +
    async def slackLists_items_update(
    +    self,
    +    *,
    +    list_id: str,
    +    cells: List[Dict[str, Any]],
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Updates cells in a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.update
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "cells": cells,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.items.update", json=kwargs)
    +
    + +
    +
    +async def slackLists_update(self,
    *,
    id: str,
    name: str | None = None,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> AsyncSlackResponse
    +
    +
    +
    + +Expand source code + +
    async def slackLists_update(
    +    self,
    +    *,
    +    id: str,
    +    name: Optional[str] = None,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> AsyncSlackResponse:
    +    """Update a List.
    +    https://docs.slack.dev/reference/methods/slackLists.update
    +    """
    +    kwargs.update(
    +        {
    +            "id": id,
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return await self.api_call("slackLists.update", json=kwargs)
    +
    + +
    async def stars_add(self,
    *,
    channel: str | None = None,
    file: str | None = None,
    file_comment: str | None = None,
    timestamp: str | None = None,
    **kwargs) ‑> AsyncSlackResponse
    @@ -14968,6 +15649,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15037,6 +15719,18 @@

    search_all
  • search_files
  • search_messages
  • +
  • slackLists_access_delete
  • +
  • slackLists_access_set
  • +
  • slackLists_create
  • +
  • slackLists_download_get
  • +
  • slackLists_download_start
  • +
  • slackLists_items_create
  • +
  • slackLists_items_delete
  • +
  • slackLists_items_deleteMultiple
  • +
  • slackLists_items_info
  • +
  • slackLists_items_list
  • +
  • slackLists_items_update
  • +
  • slackLists_update
  • stars_add
  • stars_list
  • stars_remove
  • diff --git a/docs/reference/web/client.html b/docs/reference/web/client.html index 6be773f23..6fded11e1 100644 --- a/docs/reference/web/client.html +++ b/docs/reference/web/client.html @@ -2790,7 +2790,7 @@

    Classes

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -3019,6 +3019,7 @@

    Classes

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3035,6 +3036,7 @@

    Classes

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3666,6 +3668,30 @@

    Classes

    kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[Union[Dict, EntityMetadata]] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> SlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, @@ -4920,6 +4946,249 @@

    Classes

    ) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> SlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> SlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> SlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> SlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> SlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, @@ -10066,7 +10335,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.postEphemeral

    -def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | EventAndEntityMetadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10092,7 +10361,7 @@

    Methods

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -10414,7 +10683,7 @@

    Example

    -def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    metadata: Dict | EventAndEntityMetadata | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10429,6 +10698,7 @@

    Example

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -10445,6 +10715,7 @@

    Example

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -11476,6 +11747,41 @@

    Example

    +
    +def entity_presentDetails(self,
    trigger_id: str,
    metadata: Dict | EntityMetadata | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    error: Dict[str, Any] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def entity_presentDetails(
    +    self,
    +    trigger_id: str,
    +    metadata: Optional[Union[Dict, EntityMetadata]] = None,
    +    user_auth_required: Optional[bool] = None,
    +    user_auth_url: Optional[str] = None,
    +    error: Optional[Dict[str, Any]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Provides entity details for the flexpane.
    +    https://docs.slack.dev/reference/methods/entity.presentDetails/
    +    """
    +    kwargs.update({"trigger_id": trigger_id})
    +    if metadata is not None:
    +        kwargs.update({"metadata": metadata})
    +    if user_auth_required is not None:
    +        kwargs.update({"user_auth_required": user_auth_required})
    +    if user_auth_url is not None:
    +        kwargs.update({"user_auth_url": user_auth_url})
    +    if error is not None:
    +        kwargs.update({"error": error})
    +    _parse_web_class_objects(kwargs)
    +    return self.api_call("entity.presentDetails", json=kwargs)
    +
    +

    Provides entity details for the flexpane. +https://docs.slack.dev/reference/methods/entity.presentDetails/

    +
    def files_comments_delete(self, *, file: str, id: str, **kwargs) ‑> SlackResponse
    @@ -13451,6 +13757,381 @@

    Example

    Searches for messages matching a query. https://docs.slack.dev/reference/methods/search.messages

    +
    +def slackLists_access_delete(self,
    *,
    list_id: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Revoke access to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.delete
    +    """
    +    kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.delete", json=kwargs)
    +
    +

    Revoke access to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.delete

    +
    +
    +def slackLists_access_set(self,
    *,
    list_id: str,
    access_level: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_set(
    +    self,
    +    *,
    +    list_id: str,
    +    access_level: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Set the access level to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.set
    +    """
    +    kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.set", json=kwargs)
    +
    +

    Set the access level to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.set

    +
    +
    +def slackLists_create(self,
    *,
    name: str,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    schema: List[Dict[str, Any]] | None = None,
    copy_from_list_id: str | None = None,
    include_copied_list_records: bool | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_create(
    +    self,
    +    *,
    +    name: str,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    schema: Optional[List[Dict[str, Any]]] = None,
    +    copy_from_list_id: Optional[str] = None,
    +    include_copied_list_records: Optional[bool] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Creates a List.
    +    https://docs.slack.dev/reference/methods/slackLists.create
    +    """
    +    kwargs.update(
    +        {
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "schema": schema,
    +            "copy_from_list_id": copy_from_list_id,
    +            "include_copied_list_records": include_copied_list_records,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.create", json=kwargs)
    +
    + +
    +
    +def slackLists_download_get(self, *, list_id: str, job_id: str, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_get(
    +    self,
    +    *,
    +    list_id: str,
    +    job_id: str,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Retrieve List download URL from an export job to download List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.get
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "job_id": job_id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.get", json=kwargs)
    +
    +

    Retrieve List download URL from an export job to download List contents. +https://docs.slack.dev/reference/methods/slackLists.download.get

    +
    +
    +def slackLists_download_start(self, *, list_id: str, include_archived: bool | None = None, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_start(
    +    self,
    +    *,
    +    list_id: str,
    +    include_archived: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Initiate a job to export List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.start
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "include_archived": include_archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.start", json=kwargs)
    +
    + +
    +
    +def slackLists_items_create(self,
    *,
    list_id: str,
    duplicated_item_id: str | None = None,
    parent_item_id: str | None = None,
    initial_fields: List[Dict[str, Any]] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_create(
    +    self,
    +    *,
    +    list_id: str,
    +    duplicated_item_id: Optional[str] = None,
    +    parent_item_id: Optional[str] = None,
    +    initial_fields: Optional[List[Dict[str, Any]]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Add a new item to an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.create
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "duplicated_item_id": duplicated_item_id,
    +            "parent_item_id": parent_item_id,
    +            "initial_fields": initial_fields,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.create", json=kwargs)
    +
    + +
    +
    +def slackLists_items_delete(self, *, list_id: str, id: str, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Deletes an item from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.delete
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.delete", json=kwargs)
    +
    + +
    +
    +def slackLists_items_deleteMultiple(self, *, list_id: str, ids: List[str], **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_deleteMultiple(
    +    self,
    +    *,
    +    list_id: str,
    +    ids: List[str],
    +    **kwargs,
    +) -> SlackResponse:
    +    """Deletes multiple items from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "ids": ids,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.deleteMultiple", json=kwargs)
    +
    + +
    +
    +def slackLists_items_info(self, *, list_id: str, id: str, include_is_subscribed: bool | None = None, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_info(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    include_is_subscribed: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Get a row from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.info
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +            "include_is_subscribed": include_is_subscribed,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.info", json=kwargs)
    +
    + +
    +
    +def slackLists_items_list(self,
    *,
    list_id: str,
    limit: int | None = None,
    cursor: str | None = None,
    archived: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_list(
    +    self,
    +    *,
    +    list_id: str,
    +    limit: Optional[int] = None,
    +    cursor: Optional[str] = None,
    +    archived: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Get records from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.list
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "limit": limit,
    +            "cursor": cursor,
    +            "archived": archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.list", json=kwargs)
    +
    + +
    +
    +def slackLists_items_update(self, *, list_id: str, cells: List[Dict[str, Any]], **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_update(
    +    self,
    +    *,
    +    list_id: str,
    +    cells: List[Dict[str, Any]],
    +    **kwargs,
    +) -> SlackResponse:
    +    """Updates cells in a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.update
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "cells": cells,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.update", json=kwargs)
    +
    + +
    +
    +def slackLists_update(self,
    *,
    id: str,
    name: str | None = None,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_update(
    +    self,
    +    *,
    +    id: str,
    +    name: Optional[str] = None,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Update a List.
    +    https://docs.slack.dev/reference/methods/slackLists.update
    +    """
    +    kwargs.update(
    +        {
    +            "id": id,
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.update", json=kwargs)
    +
    + +
    def stars_add(self,
    *,
    channel: str | None = None,
    file: str | None = None,
    file_comment: str | None = None,
    timestamp: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -14967,6 +15648,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15036,6 +15718,18 @@

    search_all
  • search_files
  • search_messages
  • +
  • slackLists_access_delete
  • +
  • slackLists_access_set
  • +
  • slackLists_create
  • +
  • slackLists_download_get
  • +
  • slackLists_download_start
  • +
  • slackLists_items_create
  • +
  • slackLists_items_delete
  • +
  • slackLists_items_deleteMultiple
  • +
  • slackLists_items_info
  • +
  • slackLists_items_list
  • +
  • slackLists_items_update
  • +
  • slackLists_update
  • stars_add
  • stars_list
  • stars_remove
  • diff --git a/docs/reference/web/index.html b/docs/reference/web/index.html index f4f7e2f2b..611a26b3b 100644 --- a/docs/reference/web/index.html +++ b/docs/reference/web/index.html @@ -3159,7 +3159,7 @@

    Raises

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -3388,6 +3388,7 @@

    Raises

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -3404,6 +3405,7 @@

    Raises

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -4035,6 +4037,30 @@

    Raises

    kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[Union[Dict, EntityMetadata]] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> SlackResponse: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, @@ -5289,6 +5315,249 @@

    Raises

    ) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> SlackResponse: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> SlackResponse: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> SlackResponse: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> SlackResponse: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> SlackResponse: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> SlackResponse: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> SlackResponse: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, @@ -10435,7 +10704,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.postEphemeral

    -def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | EventAndEntityMetadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10461,7 +10730,7 @@

    Methods

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> SlackResponse: @@ -10783,7 +11052,7 @@

    Example

    -def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    metadata: Dict | EventAndEntityMetadata | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10798,6 +11067,7 @@

    Example

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -10814,6 +11084,7 @@

    Example

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -11845,6 +12116,41 @@

    Example

    +
    +def entity_presentDetails(self,
    trigger_id: str,
    metadata: Dict | EntityMetadata | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    error: Dict[str, Any] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def entity_presentDetails(
    +    self,
    +    trigger_id: str,
    +    metadata: Optional[Union[Dict, EntityMetadata]] = None,
    +    user_auth_required: Optional[bool] = None,
    +    user_auth_url: Optional[str] = None,
    +    error: Optional[Dict[str, Any]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Provides entity details for the flexpane.
    +    https://docs.slack.dev/reference/methods/entity.presentDetails/
    +    """
    +    kwargs.update({"trigger_id": trigger_id})
    +    if metadata is not None:
    +        kwargs.update({"metadata": metadata})
    +    if user_auth_required is not None:
    +        kwargs.update({"user_auth_required": user_auth_required})
    +    if user_auth_url is not None:
    +        kwargs.update({"user_auth_url": user_auth_url})
    +    if error is not None:
    +        kwargs.update({"error": error})
    +    _parse_web_class_objects(kwargs)
    +    return self.api_call("entity.presentDetails", json=kwargs)
    +
    +

    Provides entity details for the flexpane. +https://docs.slack.dev/reference/methods/entity.presentDetails/

    +
    def files_comments_delete(self, *, file: str, id: str, **kwargs) ‑> SlackResponse
    @@ -13820,6 +14126,381 @@

    Example

    Searches for messages matching a query. https://docs.slack.dev/reference/methods/search.messages

    +
    +def slackLists_access_delete(self,
    *,
    list_id: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Revoke access to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.delete
    +    """
    +    kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.delete", json=kwargs)
    +
    +

    Revoke access to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.delete

    +
    +
    +def slackLists_access_set(self,
    *,
    list_id: str,
    access_level: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_set(
    +    self,
    +    *,
    +    list_id: str,
    +    access_level: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Set the access level to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.set
    +    """
    +    kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.set", json=kwargs)
    +
    +

    Set the access level to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.set

    +
    +
    +def slackLists_create(self,
    *,
    name: str,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    schema: List[Dict[str, Any]] | None = None,
    copy_from_list_id: str | None = None,
    include_copied_list_records: bool | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_create(
    +    self,
    +    *,
    +    name: str,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    schema: Optional[List[Dict[str, Any]]] = None,
    +    copy_from_list_id: Optional[str] = None,
    +    include_copied_list_records: Optional[bool] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Creates a List.
    +    https://docs.slack.dev/reference/methods/slackLists.create
    +    """
    +    kwargs.update(
    +        {
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "schema": schema,
    +            "copy_from_list_id": copy_from_list_id,
    +            "include_copied_list_records": include_copied_list_records,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.create", json=kwargs)
    +
    + +
    +
    +def slackLists_download_get(self, *, list_id: str, job_id: str, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_get(
    +    self,
    +    *,
    +    list_id: str,
    +    job_id: str,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Retrieve List download URL from an export job to download List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.get
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "job_id": job_id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.get", json=kwargs)
    +
    +

    Retrieve List download URL from an export job to download List contents. +https://docs.slack.dev/reference/methods/slackLists.download.get

    +
    +
    +def slackLists_download_start(self, *, list_id: str, include_archived: bool | None = None, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_start(
    +    self,
    +    *,
    +    list_id: str,
    +    include_archived: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Initiate a job to export List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.start
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "include_archived": include_archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.start", json=kwargs)
    +
    + +
    +
    +def slackLists_items_create(self,
    *,
    list_id: str,
    duplicated_item_id: str | None = None,
    parent_item_id: str | None = None,
    initial_fields: List[Dict[str, Any]] | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_create(
    +    self,
    +    *,
    +    list_id: str,
    +    duplicated_item_id: Optional[str] = None,
    +    parent_item_id: Optional[str] = None,
    +    initial_fields: Optional[List[Dict[str, Any]]] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Add a new item to an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.create
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "duplicated_item_id": duplicated_item_id,
    +            "parent_item_id": parent_item_id,
    +            "initial_fields": initial_fields,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.create", json=kwargs)
    +
    + +
    +
    +def slackLists_items_delete(self, *, list_id: str, id: str, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Deletes an item from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.delete
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.delete", json=kwargs)
    +
    + +
    +
    +def slackLists_items_deleteMultiple(self, *, list_id: str, ids: List[str], **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_deleteMultiple(
    +    self,
    +    *,
    +    list_id: str,
    +    ids: List[str],
    +    **kwargs,
    +) -> SlackResponse:
    +    """Deletes multiple items from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "ids": ids,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.deleteMultiple", json=kwargs)
    +
    + +
    +
    +def slackLists_items_info(self, *, list_id: str, id: str, include_is_subscribed: bool | None = None, **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_info(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    include_is_subscribed: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Get a row from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.info
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +            "include_is_subscribed": include_is_subscribed,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.info", json=kwargs)
    +
    + +
    +
    +def slackLists_items_list(self,
    *,
    list_id: str,
    limit: int | None = None,
    cursor: str | None = None,
    archived: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_list(
    +    self,
    +    *,
    +    list_id: str,
    +    limit: Optional[int] = None,
    +    cursor: Optional[str] = None,
    +    archived: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Get records from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.list
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "limit": limit,
    +            "cursor": cursor,
    +            "archived": archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.list", json=kwargs)
    +
    + +
    +
    +def slackLists_items_update(self, *, list_id: str, cells: List[Dict[str, Any]], **kwargs) ‑> SlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_update(
    +    self,
    +    *,
    +    list_id: str,
    +    cells: List[Dict[str, Any]],
    +    **kwargs,
    +) -> SlackResponse:
    +    """Updates cells in a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.update
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "cells": cells,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.update", json=kwargs)
    +
    + +
    +
    +def slackLists_update(self,
    *,
    id: str,
    name: str | None = None,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> SlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_update(
    +    self,
    +    *,
    +    id: str,
    +    name: Optional[str] = None,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> SlackResponse:
    +    """Update a List.
    +    https://docs.slack.dev/reference/methods/slackLists.update
    +    """
    +    kwargs.update(
    +        {
    +            "id": id,
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.update", json=kwargs)
    +
    + +
    def stars_add(self,
    *,
    channel: str | None = None,
    file: str | None = None,
    file_comment: str | None = None,
    timestamp: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -15362,6 +16043,7 @@

    Web
  • dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15431,6 +16113,18 @@

    Web
  • search_all
  • search_files
  • search_messages
  • +
  • slackLists_access_delete
  • +
  • slackLists_access_set
  • +
  • slackLists_create
  • +
  • slackLists_download_get
  • +
  • slackLists_download_start
  • +
  • slackLists_items_create
  • +
  • slackLists_items_delete
  • +
  • slackLists_items_deleteMultiple
  • +
  • slackLists_items_info
  • +
  • slackLists_items_list
  • +
  • slackLists_items_update
  • +
  • slackLists_update
  • stars_add
  • stars_list
  • stars_remove
  • diff --git a/docs/reference/web/legacy_client.html b/docs/reference/web/legacy_client.html index 70cc6988d..d100178dc 100644 --- a/docs/reference/web/legacy_client.html +++ b/docs/reference/web/legacy_client.html @@ -2789,7 +2789,7 @@

    Classes

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> Union[Future, SlackResponse]: @@ -2955,6 +2955,7 @@

    Classes

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -2971,6 +2972,7 @@

    Classes

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -3602,6 +3604,30 @@

    Classes

    kwargs.update({"include_categories": include_categories}) return self.api_call("emoji.list", http_verb="GET", params=kwargs) + def entity_presentDetails( + self, + trigger_id: str, + metadata: Optional[Union[Dict, EntityMetadata]] = None, + user_auth_required: Optional[bool] = None, + user_auth_url: Optional[str] = None, + error: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Provides entity details for the flexpane. + https://docs.slack.dev/reference/methods/entity.presentDetails/ + """ + kwargs.update({"trigger_id": trigger_id}) + if metadata is not None: + kwargs.update({"metadata": metadata}) + if user_auth_required is not None: + kwargs.update({"user_auth_required": user_auth_required}) + if user_auth_url is not None: + kwargs.update({"user_auth_url": user_auth_url}) + if error is not None: + kwargs.update({"error": error}) + _parse_web_class_objects(kwargs) + return self.api_call("entity.presentDetails", json=kwargs) + def files_comments_delete( self, *, @@ -4856,6 +4882,249 @@

    Classes

    ) return self.api_call("search.messages", http_verb="GET", params=kwargs) + def slackLists_access_delete( + self, + *, + list_id: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Revoke access to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.delete + """ + kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.delete", json=kwargs) + + def slackLists_access_set( + self, + *, + list_id: str, + access_level: str, + channel_ids: Optional[List[str]] = None, + user_ids: Optional[List[str]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Set the access level to a List for specified entities. + https://docs.slack.dev/reference/methods/slackLists.access.set + """ + kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids}) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.access.set", json=kwargs) + + def slackLists_create( + self, + *, + name: str, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + schema: Optional[List[Dict[str, Any]]] = None, + copy_from_list_id: Optional[str] = None, + include_copied_list_records: Optional[bool] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Creates a List. + https://docs.slack.dev/reference/methods/slackLists.create + """ + kwargs.update( + { + "name": name, + "description_blocks": description_blocks, + "schema": schema, + "copy_from_list_id": copy_from_list_id, + "include_copied_list_records": include_copied_list_records, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.create", json=kwargs) + + def slackLists_download_get( + self, + *, + list_id: str, + job_id: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Retrieve List download URL from an export job to download List contents. + https://docs.slack.dev/reference/methods/slackLists.download.get + """ + kwargs.update( + { + "list_id": list_id, + "job_id": job_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.get", json=kwargs) + + def slackLists_download_start( + self, + *, + list_id: str, + include_archived: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Initiate a job to export List contents. + https://docs.slack.dev/reference/methods/slackLists.download.start + """ + kwargs.update( + { + "list_id": list_id, + "include_archived": include_archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.download.start", json=kwargs) + + def slackLists_items_create( + self, + *, + list_id: str, + duplicated_item_id: Optional[str] = None, + parent_item_id: Optional[str] = None, + initial_fields: Optional[List[Dict[str, Any]]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Add a new item to an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.create + """ + kwargs.update( + { + "list_id": list_id, + "duplicated_item_id": duplicated_item_id, + "parent_item_id": parent_item_id, + "initial_fields": initial_fields, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.create", json=kwargs) + + def slackLists_items_delete( + self, + *, + list_id: str, + id: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Deletes an item from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.delete + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.delete", json=kwargs) + + def slackLists_items_deleteMultiple( + self, + *, + list_id: str, + ids: List[str], + **kwargs, + ) -> Union[Future, SlackResponse]: + """Deletes multiple items from an existing List. + https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple + """ + kwargs.update( + { + "list_id": list_id, + "ids": ids, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.deleteMultiple", json=kwargs) + + def slackLists_items_info( + self, + *, + list_id: str, + id: str, + include_is_subscribed: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Get a row from a List. + https://docs.slack.dev/reference/methods/slackLists.items.info + """ + kwargs.update( + { + "list_id": list_id, + "id": id, + "include_is_subscribed": include_is_subscribed, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.info", json=kwargs) + + def slackLists_items_list( + self, + *, + list_id: str, + limit: Optional[int] = None, + cursor: Optional[str] = None, + archived: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Get records from a List. + https://docs.slack.dev/reference/methods/slackLists.items.list + """ + kwargs.update( + { + "list_id": list_id, + "limit": limit, + "cursor": cursor, + "archived": archived, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.list", json=kwargs) + + def slackLists_items_update( + self, + *, + list_id: str, + cells: List[Dict[str, Any]], + **kwargs, + ) -> Union[Future, SlackResponse]: + """Updates cells in a List. + https://docs.slack.dev/reference/methods/slackLists.items.update + """ + kwargs.update( + { + "list_id": list_id, + "cells": cells, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.items.update", json=kwargs) + + def slackLists_update( + self, + *, + id: str, + name: Optional[str] = None, + description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None, + todo_mode: Optional[bool] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Update a List. + https://docs.slack.dev/reference/methods/slackLists.update + """ + kwargs.update( + { + "id": id, + "name": name, + "description_blocks": description_blocks, + "todo_mode": todo_mode, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("slackLists.update", json=kwargs) + def stars_add( self, *, @@ -10002,7 +10271,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.postEphemeral

    -def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_postMessage(self,
    *,
    channel: str,
    text: str | None = None,
    as_user: bool | None = None,
    attachments: str | Sequence[Dict | Attachment] | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    thread_ts: str | None = None,
    reply_broadcast: bool | None = None,
    unfurl_links: bool | None = None,
    unfurl_media: bool | None = None,
    container_id: str | None = None,
    icon_emoji: str | None = None,
    icon_url: str | None = None,
    mrkdwn: bool | None = None,
    link_names: bool | None = None,
    username: str | None = None,
    parse: str | None = None,
    metadata: Dict | Metadata | EventAndEntityMetadata | None = None,
    markdown_text: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10028,7 +10297,7 @@

    Methods

    link_names: Optional[bool] = None, username: Optional[str] = None, parse: Optional[str] = None, # none, full - metadata: Optional[Union[Dict, Metadata]] = None, + metadata: Optional[Union[Dict, Metadata, EventAndEntityMetadata]] = None, markdown_text: Optional[str] = None, **kwargs, ) -> Union[Future, SlackResponse]: @@ -10234,7 +10503,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.stopStream

    -def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_unfurl(self,
    *,
    channel: str | None = None,
    ts: str | None = None,
    source: str | None = None,
    unfurl_id: str | None = None,
    unfurls: Dict[str, Dict] | None = None,
    metadata: Dict | EventAndEntityMetadata | None = None,
    user_auth_blocks: str | Sequence[Dict | Block] | None = None,
    user_auth_message: str | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10249,6 +10518,7 @@

    Methods

    source: Optional[str] = None, unfurl_id: Optional[str] = None, unfurls: Optional[Dict[str, Dict]] = None, # or user_auth_* + metadata: Optional[Union[Dict, EventAndEntityMetadata]] = None, user_auth_blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, user_auth_message: Optional[str] = None, user_auth_required: Optional[bool] = None, @@ -10265,6 +10535,7 @@

    Methods

    "source": source, "unfurl_id": unfurl_id, "unfurls": unfurls, + "metadata": metadata, "user_auth_blocks": user_auth_blocks, "user_auth_message": user_auth_message, "user_auth_required": user_auth_required, @@ -11296,6 +11567,41 @@

    Methods

    +
    +def entity_presentDetails(self,
    trigger_id: str,
    metadata: Dict | EntityMetadata | None = None,
    user_auth_required: bool | None = None,
    user_auth_url: str | None = None,
    error: Dict[str, Any] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def entity_presentDetails(
    +    self,
    +    trigger_id: str,
    +    metadata: Optional[Union[Dict, EntityMetadata]] = None,
    +    user_auth_required: Optional[bool] = None,
    +    user_auth_url: Optional[str] = None,
    +    error: Optional[Dict[str, Any]] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Provides entity details for the flexpane.
    +    https://docs.slack.dev/reference/methods/entity.presentDetails/
    +    """
    +    kwargs.update({"trigger_id": trigger_id})
    +    if metadata is not None:
    +        kwargs.update({"metadata": metadata})
    +    if user_auth_required is not None:
    +        kwargs.update({"user_auth_required": user_auth_required})
    +    if user_auth_url is not None:
    +        kwargs.update({"user_auth_url": user_auth_url})
    +    if error is not None:
    +        kwargs.update({"error": error})
    +    _parse_web_class_objects(kwargs)
    +    return self.api_call("entity.presentDetails", json=kwargs)
    +
    +

    Provides entity details for the flexpane. +https://docs.slack.dev/reference/methods/entity.presentDetails/

    +
    def files_comments_delete(self, *, file: str, id: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -13271,6 +13577,381 @@

    Methods

    Searches for messages matching a query. https://docs.slack.dev/reference/methods/search.messages

    +
    +def slackLists_access_delete(self,
    *,
    list_id: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Revoke access to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.delete
    +    """
    +    kwargs.update({"list_id": list_id, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.delete", json=kwargs)
    +
    +

    Revoke access to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.delete

    +
    +
    +def slackLists_access_set(self,
    *,
    list_id: str,
    access_level: str,
    channel_ids: List[str] | None = None,
    user_ids: List[str] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_access_set(
    +    self,
    +    *,
    +    list_id: str,
    +    access_level: str,
    +    channel_ids: Optional[List[str]] = None,
    +    user_ids: Optional[List[str]] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Set the access level to a List for specified entities.
    +    https://docs.slack.dev/reference/methods/slackLists.access.set
    +    """
    +    kwargs.update({"list_id": list_id, "access_level": access_level, "channel_ids": channel_ids, "user_ids": user_ids})
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.access.set", json=kwargs)
    +
    +

    Set the access level to a List for specified entities. +https://docs.slack.dev/reference/methods/slackLists.access.set

    +
    +
    +def slackLists_create(self,
    *,
    name: str,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    schema: List[Dict[str, Any]] | None = None,
    copy_from_list_id: str | None = None,
    include_copied_list_records: bool | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_create(
    +    self,
    +    *,
    +    name: str,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    schema: Optional[List[Dict[str, Any]]] = None,
    +    copy_from_list_id: Optional[str] = None,
    +    include_copied_list_records: Optional[bool] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Creates a List.
    +    https://docs.slack.dev/reference/methods/slackLists.create
    +    """
    +    kwargs.update(
    +        {
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "schema": schema,
    +            "copy_from_list_id": copy_from_list_id,
    +            "include_copied_list_records": include_copied_list_records,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.create", json=kwargs)
    +
    + +
    +
    +def slackLists_download_get(self, *, list_id: str, job_id: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_get(
    +    self,
    +    *,
    +    list_id: str,
    +    job_id: str,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Retrieve List download URL from an export job to download List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.get
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "job_id": job_id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.get", json=kwargs)
    +
    +

    Retrieve List download URL from an export job to download List contents. +https://docs.slack.dev/reference/methods/slackLists.download.get

    +
    +
    +def slackLists_download_start(self, *, list_id: str, include_archived: bool | None = None, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_download_start(
    +    self,
    +    *,
    +    list_id: str,
    +    include_archived: Optional[bool] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Initiate a job to export List contents.
    +    https://docs.slack.dev/reference/methods/slackLists.download.start
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "include_archived": include_archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.download.start", json=kwargs)
    +
    + +
    +
    +def slackLists_items_create(self,
    *,
    list_id: str,
    duplicated_item_id: str | None = None,
    parent_item_id: str | None = None,
    initial_fields: List[Dict[str, Any]] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_create(
    +    self,
    +    *,
    +    list_id: str,
    +    duplicated_item_id: Optional[str] = None,
    +    parent_item_id: Optional[str] = None,
    +    initial_fields: Optional[List[Dict[str, Any]]] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Add a new item to an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.create
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "duplicated_item_id": duplicated_item_id,
    +            "parent_item_id": parent_item_id,
    +            "initial_fields": initial_fields,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.create", json=kwargs)
    +
    + +
    +
    +def slackLists_items_delete(self, *, list_id: str, id: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_delete(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Deletes an item from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.delete
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.delete", json=kwargs)
    +
    + +
    +
    +def slackLists_items_deleteMultiple(self, *, list_id: str, ids: List[str], **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_deleteMultiple(
    +    self,
    +    *,
    +    list_id: str,
    +    ids: List[str],
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Deletes multiple items from an existing List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "ids": ids,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.deleteMultiple", json=kwargs)
    +
    + +
    +
    +def slackLists_items_info(self, *, list_id: str, id: str, include_is_subscribed: bool | None = None, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_info(
    +    self,
    +    *,
    +    list_id: str,
    +    id: str,
    +    include_is_subscribed: Optional[bool] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Get a row from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.info
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "id": id,
    +            "include_is_subscribed": include_is_subscribed,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.info", json=kwargs)
    +
    + +
    +
    +def slackLists_items_list(self,
    *,
    list_id: str,
    limit: int | None = None,
    cursor: str | None = None,
    archived: bool | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_items_list(
    +    self,
    +    *,
    +    list_id: str,
    +    limit: Optional[int] = None,
    +    cursor: Optional[str] = None,
    +    archived: Optional[bool] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Get records from a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.list
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "limit": limit,
    +            "cursor": cursor,
    +            "archived": archived,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.list", json=kwargs)
    +
    + +
    +
    +def slackLists_items_update(self, *, list_id: str, cells: List[Dict[str, Any]], **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
    +
    +
    + +Expand source code + +
    def slackLists_items_update(
    +    self,
    +    *,
    +    list_id: str,
    +    cells: List[Dict[str, Any]],
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Updates cells in a List.
    +    https://docs.slack.dev/reference/methods/slackLists.items.update
    +    """
    +    kwargs.update(
    +        {
    +            "list_id": list_id,
    +            "cells": cells,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.items.update", json=kwargs)
    +
    + +
    +
    +def slackLists_update(self,
    *,
    id: str,
    name: str | None = None,
    description_blocks: str | Sequence[Dict | RichTextBlock] | None = None,
    todo_mode: bool | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +
    +
    +
    + +Expand source code + +
    def slackLists_update(
    +    self,
    +    *,
    +    id: str,
    +    name: Optional[str] = None,
    +    description_blocks: Optional[Union[str, Sequence[Union[Dict, RichTextBlock]]]] = None,
    +    todo_mode: Optional[bool] = None,
    +    **kwargs,
    +) -> Union[Future, SlackResponse]:
    +    """Update a List.
    +    https://docs.slack.dev/reference/methods/slackLists.update
    +    """
    +    kwargs.update(
    +        {
    +            "id": id,
    +            "name": name,
    +            "description_blocks": description_blocks,
    +            "todo_mode": todo_mode,
    +        }
    +    )
    +    kwargs = _remove_none_values(kwargs)
    +    return self.api_call("slackLists.update", json=kwargs)
    +
    + +
    def stars_add(self,
    *,
    channel: str | None = None,
    file: str | None = None,
    file_comment: str | None = None,
    timestamp: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -14785,6 +15466,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -14854,6 +15536,18 @@

    search_all
  • search_files
  • search_messages
  • +
  • slackLists_access_delete
  • +
  • slackLists_access_set
  • +
  • slackLists_create
  • +
  • slackLists_download_get
  • +
  • slackLists_download_start
  • +
  • slackLists_items_create
  • +
  • slackLists_items_delete
  • +
  • slackLists_items_deleteMultiple
  • +
  • slackLists_items_info
  • +
  • slackLists_items_list
  • +
  • slackLists_items_update
  • +
  • slackLists_update
  • stars_add
  • stars_list
  • stars_remove
  • diff --git a/slack_sdk/version.py b/slack_sdk/version.py index c787aa278..fb572e0ba 100644 --- a/slack_sdk/version.py +++ b/slack_sdk/version.py @@ -1,3 +1,3 @@ """Check the latest version at https://pypi.org/project/slack-sdk/""" -__version__ = "3.38.0" +__version__ = "3.39.0" From 1244019c77c17da56526cce0381bbc9063bda78c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:20:53 -0500 Subject: [PATCH 08/30] chore(deps): bump actions/checkout from 5.0.0 to 6.0.0 (#1799) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 21b472247..24ae2eb75 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: ref: ${{ github.event.release.tag_name || github.ref }} persist-credentials: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92ed007c8..18db5d80d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} @@ -54,7 +54,7 @@ jobs: CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" FORCE_COLOR: "1" steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} From fe1f38eea302a0a7716763fee136b82f0bf86ab9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:33:27 -0500 Subject: [PATCH 09/30] chore(deps): bump actions/setup-python from 6.0.0 to 6.1.0 (#1800) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Bergamin --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 24ae2eb75..80fac6d8c 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.x" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18db5d80d..ac34e26fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.python-version }} - name: Run mypy verification @@ -58,7 +58,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.python-version }} cache: pip From 9b276eb4b64624397e52c0d7df12dc6798778531 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:03:18 -0500 Subject: [PATCH 10/30] chore(deps): bump docutils from 0.22.2 to 0.22.3 (#1802) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/documentation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/documentation.txt b/requirements/documentation.txt index af8c47639..26d7aaa41 100644 --- a/requirements/documentation.txt +++ b/requirements/documentation.txt @@ -1,2 +1,2 @@ -docutils==0.22.2 +docutils==0.22.3 pdoc3==0.11.6 From 4601f8a82836cf649ffe0f5d7b08061882d6361f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:19:24 -0500 Subject: [PATCH 11/30] chore(deps): update mypy requirement from <=1.18.2 to <=1.19.0 (#1801) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michael Brooks Co-authored-by: William Bergamin --- requirements/tools.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/tools.txt b/requirements/tools.txt index 0a646dc12..dd09cd46f 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,4 +1,6 @@ -mypy<=1.18.2 +# We only need to install mypy with CPython runtimes +# Typechecking using the PyPy runtime is not planned +mypy<=1.19.0; platform_python_implementation == "CPython" # while flake8 5.x have issues with Python 3.12, flake8 6.x requires Python >= 3.8.1, # so 5.x should be kept in order to stay compatible with Python 3.7/3.8 flake8>=5.0.4,<8 From dfa5e5f01020df1782ffd2ed65a8a781218fc664 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 5 Dec 2025 15:38:18 -0500 Subject: [PATCH 12/30] fix: improve CI and helper scripts (#1803) Co-authored-by: Eden Zimbelman --- .github/workflows/{tests.yml => ci-build.yml} | 45 +++++++++++++------ requirements/testing.txt | 1 - requirements/tools.txt | 4 +- scripts/format.sh | 7 ++- scripts/lint.sh | 14 ++++++ scripts/run_integration_tests.sh | 5 ++- scripts/run_mypy.sh | 3 +- scripts/run_unit_tests.sh | 6 ++- scripts/run_validation.sh | 9 ++-- 9 files changed, 65 insertions(+), 29 deletions(-) rename .github/workflows/{tests.yml => ci-build.yml} (79%) create mode 100755 scripts/lint.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/ci-build.yml similarity index 79% rename from .github/workflows/tests.yml rename to .github/workflows/ci-build.yml index ac34e26fd..7e9e4e28d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/ci-build.yml @@ -1,5 +1,4 @@ -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Test +name: Python CI on: push: @@ -10,27 +9,44 @@ on: - cron: "0 0 * * *" workflow_dispatch: +env: + LATEST_SUPPORTED_PY: "3.14" + jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Run lint verification + run: ./scripts/lint.sh + typecheck: - name: Typechecks + name: Typecheck runs-on: ubuntu-latest timeout-minutes: 5 - strategy: - matrix: - python-version: ["3.14"] permissions: contents: read steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ env.LATEST_SUPPORTED_PY }} - name: Run mypy verification - run: | - ./scripts/run_mypy.sh + run: ./scripts/run_mypy.sh + unittest: name: Unit tests runs-on: ubuntu-22.04 @@ -48,6 +64,7 @@ jobs: - "3.8" - "3.7" - "pypy3.10" + - "pypy3.11" permissions: contents: read env: @@ -67,10 +84,8 @@ jobs: pip install -U pip pip install -r requirements/testing.txt pip install -r requirements/optional.txt - - name: Run validation (black/flake8/pytest) + - name: Run tests run: | - black --check slack/ slack_sdk/ tests/ integration_tests/ - flake8 slack/ slack_sdk/ PYTHONPATH=$PWD:$PYTHONPATH pytest --cov-report=xml --cov=slack_sdk/ --junitxml=reports/test_report.xml tests/ - name: Run tests for SQLAlchemy v1.4 (backward-compatibility) run: | @@ -89,7 +104,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true - name: Upload test coverage to Codecov (only with latest supported version) - if: startsWith(matrix.python-version, '3.14') + if: startsWith(matrix.python-version, env.LATEST_SUPPORTED_PY) uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: fail_ci_if_error: true @@ -98,10 +113,12 @@ jobs: report_type: coverage token: ${{ secrets.CODECOV_TOKEN }} verbose: true + notifications: name: Regression notifications runs-on: ubuntu-latest needs: + - lint - typecheck - unittest if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} diff --git a/requirements/testing.txt b/requirements/testing.txt index 9b702718c..9e1a3fd67 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -12,4 +12,3 @@ moto>=4.0.13,<6 # For AsyncSQLAlchemy tests greenlet<=4 aiosqlite<=1 --r tools.txt diff --git a/requirements/tools.txt b/requirements/tools.txt index dd09cd46f..4c0751dbd 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,6 +1,4 @@ -# We only need to install mypy with CPython runtimes -# Typechecking using the PyPy runtime is not planned -mypy<=1.19.0; platform_python_implementation == "CPython" +mypy<=1.19.0; # while flake8 5.x have issues with Python 3.12, flake8 6.x requires Python >= 3.8.1, # so 5.x should be kept in order to stay compatible with Python 3.7/3.8 flake8>=5.0.4,<8 diff --git a/scripts/format.sh b/scripts/format.sh index d4236258f..56ca68077 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -4,7 +4,10 @@ script_dir=`dirname $0` cd ${script_dir}/.. -pip install -U pip -pip install -U -r requirements/tools.txt +if [[ "$1" != "--no-install" ]]; then + export PIP_REQUIRE_VIRTUALENV=1 + pip install -U pip + pip install -U -r requirements/tools.txt +fi black slack/ slack_sdk/ tests/ integration_tests/ diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 000000000..8fac67888 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,14 @@ + +#!/bin/bash +# ./scripts/lint.sh + +script_dir=`dirname $0` +cd ${script_dir}/.. + +if [[ "$1" != "--no-install" ]]; then + pip install -U pip + pip install -U -r requirements/tools.txt +fi + +black --check slack/ slack_sdk/ tests/ integration_tests/ +flake8 slack/ slack_sdk/ diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index a5d86801b..1a6f254cb 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -10,10 +10,11 @@ cd ${script_dir}/.. pip install -U pip pip install -U -r requirements/testing.txt \ - -U -r requirements/optional.txt + -U -r requirements/optional.txt \ + -U -r requirements/tools.txt echo "Generating code ..." && python scripts/codegen.py --path . -echo "Running black (code formatter) ..." && black slack_sdk/ +echo "Running black (code formatter) ..." && ./scripts/format.sh --no-install test_target="${1:-tests/integration_tests/}" PYTHONPATH=$PWD:$PYTHONPATH pytest $test_target diff --git a/scripts/run_mypy.sh b/scripts/run_mypy.sh index 0d27346bc..cc1146f15 100755 --- a/scripts/run_mypy.sh +++ b/scripts/run_mypy.sh @@ -8,6 +8,7 @@ cd ${script_dir}/.. pip install -U pip setuptools wheel pip install -U -r requirements/testing.txt \ - -U -r requirements/optional.txt + -U -r requirements/optional.txt \ + -U -r requirements/tools.txt mypy --config-file pyproject.toml diff --git a/scripts/run_unit_tests.sh b/scripts/run_unit_tests.sh index cec153388..c8ab0af78 100755 --- a/scripts/run_unit_tests.sh +++ b/scripts/run_unit_tests.sh @@ -10,10 +10,12 @@ cd ${script_dir}/.. pip install -U pip pip install -U -r requirements/testing.txt \ - -U -r requirements/optional.txt + -U -r requirements/optional.txt \ + -U -r requirements/tools.txt echo "Generating code ..." && python scripts/codegen.py --path . -echo "Running black (code formatter) ..." && black slack_sdk/ +echo "Running black (code formatter) ..." && ./scripts/format.sh --no-install +echo "Running tests ..." test_target="${1:-tests/}" PYTHONPATH=$PWD:$PYTHONPATH pytest $test_target diff --git a/scripts/run_validation.sh b/scripts/run_validation.sh index 8a61d03a6..366f0d321 100755 --- a/scripts/run_validation.sh +++ b/scripts/run_validation.sh @@ -8,13 +8,14 @@ script_dir=`dirname $0` cd ${script_dir}/.. pip install -U -r requirements/testing.txt \ - -U -r requirements/optional.txt + -U -r requirements/optional.txt \ + -U -r requirements/tools.txt echo "Generating code ..." && python scripts/codegen.py --path . -echo "Running black (code formatter) ..." && black slack_sdk/ +echo "Running black (code formatter) ..." && ./scripts/format.sh --no-install -black --check slack/ slack_sdk/ tests/ integration_tests/ -flake8 slack/ slack_sdk/ +echo "Running linting checks ..." && ./scripts/lint.sh --no-install +echo "Running tests with coverage reporting ..." test_target="${1:-tests/}" PYTHONPATH=$PWD:$PYTHONPATH pytest --cov-report=xml --cov=slack_sdk/ $test_target From b7eb2f973b4497b5d0e44ff5dcc52d40a51d7daa Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 5 Dec 2025 15:55:29 -0500 Subject: [PATCH 13/30] fix: move away from datetime.utcfromtimestamp for the state and installation stores (#1798) --- integration_tests/samples/oauth/oauth_v2.py | 10 ++++- .../samples/oauth/oauth_v2_async.py | 8 +++- .../oauth/installation_store/models/bot.py | 8 ++-- .../installation_store/models/installation.py | 12 ++++-- .../oauth/state_store/sqlalchemy/__init__.py | 10 ++--- .../oauth/installation_store/test_models.py | 43 +++++++++++++++++++ 6 files changed, 76 insertions(+), 15 deletions(-) diff --git a/integration_tests/samples/oauth/oauth_v2.py b/integration_tests/samples/oauth/oauth_v2.py index 5741b4ef8..c1c32d879 100644 --- a/integration_tests/samples/oauth/oauth_v2.py +++ b/integration_tests/samples/oauth/oauth_v2.py @@ -123,13 +123,19 @@ def oauth_callback(): @app.route("/slack/events", methods=["POST"]) def slack_app(): + data = request.get_data() if not signature_verifier.is_valid( - body=request.get_data(), + body=data, timestamp=request.headers.get("X-Slack-Request-Timestamp"), signature=request.headers.get("X-Slack-Signature"), ): return make_response("invalid request", 403) + if data and b"url_verification" in data: + body = json.loads(data) + if body.get("type") == "url_verification" and "challenge" in body: + return make_response(body["challenge"], 200) + if "command" in request.form and request.form["command"] == "/open-modal": try: enterprise_id = request.form.get("enterprise_id") @@ -193,4 +199,4 @@ def slack_app(): # python3 integration_tests/samples/oauth/oauth_v2.py # ngrok http 3000 - # https://{yours}.ngrok.io/slack/oauth/start + # https://{yours}.ngrok.io/slack/install diff --git a/integration_tests/samples/oauth/oauth_v2_async.py b/integration_tests/samples/oauth/oauth_v2_async.py index c43add6f3..588b0b5f0 100644 --- a/integration_tests/samples/oauth/oauth_v2_async.py +++ b/integration_tests/samples/oauth/oauth_v2_async.py @@ -135,13 +135,19 @@ async def oauth_callback(req: Request): @app.post("/slack/events") async def slack_app(req: Request): + data = req.body.decode("utf-8") if not signature_verifier.is_valid( - body=req.body.decode("utf-8"), + body=data, timestamp=req.headers.get("X-Slack-Request-Timestamp"), signature=req.headers.get("X-Slack-Signature"), ): return HTTPResponse(status=403, body="invalid request") + if data and "url_verification" in data: + body = json.loads(data) + if body.get("type") == "url_verification" and "challenge" in body: + return HTTPResponse(status=200, body=body["challenge"]) + if "command" in req.form and req.form.get("command") == "/open-modal": try: enterprise_id = req.form.get("enterprise_id") diff --git a/slack_sdk/oauth/installation_store/models/bot.py b/slack_sdk/oauth/installation_store/models/bot.py index 52c1dac50..3f2f6de81 100644 --- a/slack_sdk/oauth/installation_store/models/bot.py +++ b/slack_sdk/oauth/installation_store/models/bot.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from time import time from typing import Optional, Union, Dict, Any, Sequence @@ -100,10 +100,12 @@ def _to_standard_value_dict(self) -> Dict[str, Any]: "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/slack_sdk/oauth/installation_store/models/installation.py b/slack_sdk/oauth/installation_store/models/installation.py index 91c6510f2..18ca8e0b1 100644 --- a/slack_sdk/oauth/installation_store/models/installation.py +++ b/slack_sdk/oauth/installation_store/models/installation.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from time import time from typing import Optional, Union, Dict, Any, Sequence @@ -173,14 +173,18 @@ def _to_standard_value_dict(self) -> Dict[str, Any]: "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -188,7 +192,7 @@ def _to_standard_value_dict(self) -> Dict[str, Any]: "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/slack_sdk/oauth/state_store/sqlalchemy/__init__.py b/slack_sdk/oauth/state_store/sqlalchemy/__init__.py index b26f642cd..8bb3ec1ff 100644 --- a/slack_sdk/oauth/state_store/sqlalchemy/__init__.py +++ b/slack_sdk/oauth/state_store/sqlalchemy/__init__.py @@ -1,6 +1,6 @@ import logging import time -from datetime import datetime +from datetime import datetime, timezone from logging import Logger from uuid import uuid4 @@ -55,7 +55,7 @@ def logger(self) -> Logger: def issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) with self.engine.begin() as conn: conn.execute( self.oauth_states.insert(), @@ -67,7 +67,7 @@ def consume(self, state: str) -> bool: try: with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -124,7 +124,7 @@ def logger(self) -> Logger: async def async_issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) async with self.engine.begin() as conn: await conn.execute( self.oauth_states.insert(), @@ -136,7 +136,7 @@ async def async_consume(self, state: str) -> bool: try: async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") diff --git a/tests/slack_sdk/oauth/installation_store/test_models.py b/tests/slack_sdk/oauth/installation_store/test_models.py index d63964be6..43b1ec7b2 100644 --- a/tests/slack_sdk/oauth/installation_store/test_models.py +++ b/tests/slack_sdk/oauth/installation_store/test_models.py @@ -1,4 +1,5 @@ import time +from datetime import datetime, timezone import unittest from slack_sdk.oauth.installation_store import Installation, FileInstallationStore, Bot @@ -36,6 +37,22 @@ def test_bot_custom_fields(self): self.assertEqual(bot.to_dict().get("service_user_id"), "XYZ123") self.assertEqual(bot.to_dict_for_copying().get("custom_values").get("service_user_id"), "XYZ123") + def test_bot_datetime_manipulation(self): + expected_timestamp = datetime.now(tz=timezone.utc) + bot = Bot( + bot_token="xoxb-", + bot_id="B111", + bot_user_id="U111", + bot_token_expires_at=expected_timestamp, + installed_at=expected_timestamp, + ) + bot_dict = bot.to_dict() + self.assertIsNotNone(bot_dict) + self.assertEqual( + bot_dict.get("bot_token_expires_at").isoformat(), expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00") + ) + self.assertEqual(bot_dict.get("installed_at"), expected_timestamp) + def test_installation(self): installation = Installation( app_id="A111", @@ -84,3 +101,29 @@ def test_installation_custom_fields(self): self.assertEqual(bot.to_dict().get("app_id"), "A111") self.assertEqual(bot.to_dict().get("service_user_id"), "XYZ123") self.assertEqual(bot.to_dict_for_copying().get("custom_values").get("app_id"), "A222") + + def test_installation_datetime_manipulation(self): + expected_timestamp = datetime.now(tz=timezone.utc) + installation = Installation( + app_id="A111", + enterprise_id="E111", + team_id="T111", + user_id="U111", + bot_id="B111", + bot_token="xoxb-111", + bot_scopes=["chat:write"], + bot_user_id="U222", + bot_token_expires_at=expected_timestamp, + user_token_expires_at=expected_timestamp, + installed_at=expected_timestamp, + ) + installation_dict = installation.to_dict() + self.assertIsNotNone(installation_dict) + self.assertEqual( + installation_dict.get("bot_token_expires_at").isoformat(), expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00") + ) + self.assertEqual( + installation_dict.get("user_token_expires_at").isoformat(), + expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00"), + ) + self.assertEqual(installation_dict.get("installed_at"), expected_timestamp) From 96c0f840029f44c071f6a233b37c5d9e477d0ff7 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Tue, 9 Dec 2025 15:29:39 -0500 Subject: [PATCH 14/30] fix: broken link to CI from readme (#1805) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 568efbea2..2d0638e7d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

    Python Slack SDK

    - - Tests + + Tests Codecov From 6fc2d5c082f70ca3e222a7644e6eb202f2140893 Mon Sep 17 00:00:00 2001 From: Luke Russell <31357343+lukegalbraithrussell@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:09:52 -0800 Subject: [PATCH 15/30] docs: updates outmoded links (#1807) --- docs/english/audit-logs.md | 2 +- docs/english/installation.md | 2 +- docs/english/legacy/auth.md | 2 +- docs/english/web.md | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/english/audit-logs.md b/docs/english/audit-logs.md index 6f10849cb..1d8b930ce 100644 --- a/docs/english/audit-logs.md +++ b/docs/english/audit-logs.md @@ -1,6 +1,6 @@ # Audit Logs API client -The [Audit Logs API](/admins/audit-logs-api) is a set of APIs that you can use to monitor what's happening in your [Enterprise Grid](/enterprise-grid) organization. +The [Audit Logs API](/admins/audit-logs-api) is a set of APIs that you can use to monitor what's happening in your [Enterprise Grid](/enterprise) organization. The Audit Logs API can be used by Security Information and Event Management (SIEM) tools to provide an analysis of how your Slack organization is being accessed. You can also use this API to write your own apps to see how members of your organization are using Slack. diff --git a/docs/english/installation.md b/docs/english/installation.md index bf818fe22..17bae95d2 100644 --- a/docs/english/installation.md +++ b/docs/english/installation.md @@ -69,7 +69,7 @@ import os SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] ``` -Refer to our [best practices for security](/authentication/best-practices-for-security) page for more information. +Refer to our [best practices for security](/security) page for more information. ## Installing on a single workspace {#single-workspace} diff --git a/docs/english/legacy/auth.md b/docs/english/legacy/auth.md index 82671d042..23fe0aa23 100644 --- a/docs/english/legacy/auth.md +++ b/docs/english/legacy/auth.md @@ -31,7 +31,7 @@ import os SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] ``` -Refer to our [best practices for security](/authentication/best-practices-for-security) page for more information. +Refer to our [best practices for security](/security) page for more information. ## Installing on a single workspace {#single-workspace} diff --git a/docs/english/web.md b/docs/english/web.md index 49d6c5871..b776ce5fd 100644 --- a/docs/english/web.md +++ b/docs/english/web.md @@ -57,9 +57,9 @@ See the [`chat.postEphemeral`](/reference/methods/chat.postEphemeral) API method You can have your app's messages stream in to replicate conventional AI chatbot behavior. This is done through three Web API methods: -* [`chat_startStream`](/reference/methods/chat.startstream) -* [`chat_appendStream`](/reference/methods/chat.appendstream) -* [`chat_stopStream`](/reference/methods/chat.stopstream) +* [`chat_startStream`](/reference/methods/chat.startStream) +* [`chat_appendStream`](/reference/methods/chat.appendStream) +* [`chat_stopStream`](/reference/methods/chat.stopStream) :::tip[The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods.] From 3aaa16d54aeb7bd03c973c6be790f3248d97ab1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:49:56 -0800 Subject: [PATCH 16/30] chore(deps): bump actions/checkout from 6.0.0 to 6.0.1 (#1817) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-build.yml | 6 +++--- .github/workflows/pypi-release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7e9e4e28d..b37185760 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} @@ -37,7 +37,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} @@ -71,7 +71,7 @@ jobs: CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" FORCE_COLOR: "1" steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 80fac6d8c..9afa946aa 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.release.tag_name || github.ref }} persist-credentials: false From 47a8d4bdbcc265072dacf5a076b919e25fa0836d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:58:00 +0000 Subject: [PATCH 17/30] chore(deps): bump actions/stale from 10.1.0 to 10.1.1 (#1816) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/triage-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/triage-issues.yml b/.github/workflows/triage-issues.yml index 85ccb72aa..cf13d3afc 100644 --- a/.github/workflows/triage-issues.yml +++ b/.github/workflows/triage-issues.yml @@ -16,7 +16,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: days-before-issue-stale: 30 days-before-issue-close: 10 From 763f64262f177ce86cd21c97ec546cc282340e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:02:24 +0000 Subject: [PATCH 18/30] chore(deps): bump docutils from 0.22.3 to 0.22.4 (#1812) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/documentation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/documentation.txt b/requirements/documentation.txt index 26d7aaa41..57304cb53 100644 --- a/requirements/documentation.txt +++ b/requirements/documentation.txt @@ -1,2 +1,2 @@ -docutils==0.22.3 +docutils==0.22.4 pdoc3==0.11.6 From 4e575b70d9e75dfe04b810c7cc5aa22f20e26b98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 07:15:39 -0800 Subject: [PATCH 19/30] chore(deps): bump codecov/codecov-action from 5.5.1 to 5.5.2 (#1815) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Bergamin --- .github/workflows/ci-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b37185760..c0fd021f0 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -95,7 +95,7 @@ jobs: PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/state_store/test_sqlalchemy.py - name: Upload test results to Codecov if: ${{ !cancelled() }} - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: directory: ./reports/ fail_ci_if_error: true @@ -105,7 +105,7 @@ jobs: verbose: true - name: Upload test coverage to Codecov (only with latest supported version) if: startsWith(matrix.python-version, env.LATEST_SUPPORTED_PY) - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: fail_ci_if_error: true # Run validation generates the coverage file From 8026611a2d6d3493a434f5f3c8852a5c63a1eb6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:23:29 +0000 Subject: [PATCH 20/30] chore(deps): bump actions/download-artifact from 6.0.0 to 7.0.0 (#1813) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 9afa946aa..7f97da9ed 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -52,7 +52,7 @@ jobs: steps: - name: Retrieve dist folder - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: release-dist path: dist/ @@ -76,7 +76,7 @@ jobs: steps: - name: Retrieve dist folder - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: release-dist path: dist/ From 6a33604c068739c4e6ee40d49402e40cb0f7f746 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:31:30 +0000 Subject: [PATCH 21/30] chore(deps): bump actions/upload-artifact from 5.0.0 to 6.0.0 (#1814) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 7f97da9ed..89a18c827 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -33,7 +33,7 @@ jobs: scripts/build_pypi_package.sh - name: Persist dist folder - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: release-dist path: dist/ From 95e80f8251292cb44be75e9f249a7dea3e054d0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:08:24 -0800 Subject: [PATCH 22/30] chore(deps): bump black from 23.3.0 to 24.3.0 in /requirements (#1786) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Bergamin --- requirements/tools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tools.txt b/requirements/tools.txt index 4c0751dbd..39946a86d 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -4,4 +4,4 @@ mypy<=1.19.0; flake8>=5.0.4,<8 # Don't change this version without running CI builds; # The latest version may not be available for older Python runtime -black==23.3.0; +black==24.3.0; From 4981f344b6fb4bee686b30fac481fce493c1dd25 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Tue, 3 Feb 2026 19:22:36 -0800 Subject: [PATCH 23/30] ci(deps): auto-approve / auto-merge dependencies from dependabot (#1830) --- .github/workflows/dependencies.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/dependencies.yml diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 000000000..824d57701 --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,29 @@ +name: Merge updates to dependencies +on: + pull_request: +jobs: + dependabot: + name: "@dependabot" + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Collect metadata + id: metadata + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Automerge + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6752322e48f4e955de0e694c8f0dce087869e257 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:04:50 -0800 Subject: [PATCH 24/30] chore(deps): update mypy requirement from <=1.19.0 to <=1.19.1 (#1827) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michael Brooks --- requirements/tools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tools.txt b/requirements/tools.txt index 39946a86d..1b43f52da 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,4 +1,4 @@ -mypy<=1.19.0; +mypy<=1.19.1; # while flake8 5.x have issues with Python 3.12, flake8 6.x requires Python >= 3.8.1, # so 5.x should be kept in order to stay compatible with Python 3.7/3.8 flake8>=5.0.4,<8 From 564f1318ad51b3c1e187c01c739196df85a5e099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:13:27 +0000 Subject: [PATCH 25/30] chore(deps): bump actions/setup-python from 6.1.0 to 6.2.0 (#1828) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michael Brooks --- .github/workflows/ci-build.yml | 6 +++--- .github/workflows/pypi-release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index c0fd021f0..a65d7e74a 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.LATEST_SUPPORTED_PY }} - name: Run lint verification @@ -41,7 +41,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.LATEST_SUPPORTED_PY }} - name: Run mypy verification @@ -75,7 +75,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} cache: pip diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 89a18c827..1f5dafcbf 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.x" From c697f3594c8383edc206e70dd10273b5aff1849c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:22:09 +0000 Subject: [PATCH 26/30] chore(deps): bump actions/checkout from 6.0.1 to 6.0.2 (#1829) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-build.yml | 6 +++--- .github/workflows/pypi-release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index a65d7e74a..c705fbb7d 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} @@ -37,7 +37,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} @@ -71,7 +71,7 @@ jobs: CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" FORCE_COLOR: "1" steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 1f5dafcbf..9c9003c92 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.release.tag_name || github.ref }} persist-credentials: false From af6a7bbdbcef995c389db1ae4229a5881daa6162 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 10 Feb 2026 13:46:49 -0800 Subject: [PATCH 27/30] feat: add thinking steps support as chunks to chat stream with plan and task blocks (#1824) Co-authored-by: Michael Brooks Co-authored-by: Luke Russell <31357343+lukegalbraithrussell@users.noreply.github.com> Co-authored-by: Maria Alejandra <104795114+srtaalej@users.noreply.github.com> --- docs/reference/index.html | 43 +- docs/reference/models/basic_objects.html | 1 + .../models/blocks/basic_components.html | 12 +- .../models/blocks/block_elements.html | 113 ++++- docs/reference/models/blocks/blocks.html | 274 ++++++++++- docs/reference/models/blocks/index.html | 397 +++++++++++++++- docs/reference/models/index.html | 1 + docs/reference/models/messages/chunk.html | 447 ++++++++++++++++++ docs/reference/models/messages/index.html | 5 + docs/reference/models/views/index.html | 4 +- .../oauth/installation_store/index.html | 16 +- .../oauth/installation_store/models/bot.html | 6 +- .../installation_store/models/index.html | 16 +- .../models/installation.html | 10 +- .../oauth/state_store/sqlalchemy/index.html | 16 +- docs/reference/web/async_chat_stream.html | 70 ++- docs/reference/web/async_client.html | 43 +- docs/reference/web/chat_stream.html | 70 ++- docs/reference/web/client.html | 43 +- docs/reference/web/index.html | 43 +- docs/reference/web/legacy_client.html | 30 +- slack_sdk/models/blocks/__init__.py | 6 + slack_sdk/models/blocks/block_elements.py | 38 ++ slack_sdk/models/blocks/blocks.py | 101 ++++ slack_sdk/models/messages/chunk.py | 134 ++++++ slack_sdk/web/async_chat_stream.py | 40 +- slack_sdk/web/async_client.py | 19 +- slack_sdk/web/chat_stream.py | 40 +- slack_sdk/web/client.py | 19 +- slack_sdk/web/internal_utils.py | 14 +- slack_sdk/web/legacy_client.py | 15 +- tests/slack_sdk/models/test_blocks.py | 82 ++++ tests/slack_sdk/models/test_chunks.py | 91 ++++ tests/slack_sdk/web/test_chat_stream.py | 84 +++- tests/slack_sdk/web/test_internal_utils.py | 41 +- .../web/test_async_chat_stream.py | 83 +++- 36 files changed, 2314 insertions(+), 153 deletions(-) create mode 100644 docs/reference/models/messages/chunk.html create mode 100644 slack_sdk/models/messages/chunk.py create mode 100644 tests/slack_sdk/models/test_chunks.py diff --git a/docs/reference/index.html b/docs/reference/index.html index 7cc7723c1..31b766b61 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -2755,7 +2755,8 @@

    Classes

    *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2766,8 +2767,10 @@

    Classes

    "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -3009,6 +3012,8 @@

    Classes

    markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -3021,8 +3026,11 @@

    Classes

    "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -3034,6 +3042,7 @@

    Classes

    markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -3046,6 +3055,7 @@

    Classes

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -3060,6 +3070,7 @@

    Classes

    thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -3086,6 +3097,8 @@

    Classes

    recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3111,6 +3124,7 @@

    Classes

    thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10248,7 +10262,7 @@

    Methods

    Unarchives a channel.

    -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10260,7 +10274,8 @@

    Methods

    *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10271,8 +10286,10 @@

    Methods

    "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
    @@ -10598,7 +10615,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

    -def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    task_display_mode: str | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10613,6 +10630,8 @@

    Methods

    markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10625,8 +10644,11 @@

    Methods

    "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
    @@ -10634,7 +10656,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.startStream

    -def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    **kwargs) ‑> SlackResponse
    +def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> SlackResponse
    @@ -10649,6 +10671,7 @@

    Methods

    markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10661,6 +10684,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10671,7 +10695,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.stopStream

    -def chat_stream(self,
    *,
    buffer_size: int = 256,
    channel: str,
    thread_ts: str,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> ChatStream
    +def chat_stream(self,
    *,
    buffer_size: int = 256,
    channel: str,
    thread_ts: str,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    task_display_mode: str | None = None,
    **kwargs) ‑> ChatStream
    @@ -10686,6 +10710,7 @@

    Methods

    thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10712,6 +10737,8 @@

    Methods

    recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10737,6 +10764,7 @@

    Methods

    thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10769,6 +10797,9 @@

    Args

    streaming to channels.
    recipient_user_id
    The encoded ID of the user to receive the streaming text. Required when streaming to channels.
    +
    task_display_mode
    +
    Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
    **kwargs
    Additional arguments passed to the underlying API calls.

    diff --git a/docs/reference/models/basic_objects.html b/docs/reference/models/basic_objects.html index 193200f84..cda8d9c95 100644 --- a/docs/reference/models/basic_objects.html +++ b/docs/reference/models/basic_objects.html @@ -212,6 +212,7 @@

    Subclasses

  • AbstractDialogSelector
  • DialogBuilder
  • DialogTextComponent
  • +
  • Chunk
  • Message
  • ContentItemEntityFields
  • EntityActionButton
  • diff --git a/docs/reference/models/blocks/basic_components.html b/docs/reference/models/blocks/basic_components.html index 2821b6a4b..50554265d 100644 --- a/docs/reference/models/blocks/basic_components.html +++ b/docs/reference/models/blocks/basic_components.html @@ -188,7 +188,7 @@

    Class variables

    Static methods

    -def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any]) +def parse(confirm: ConfirmObject | Dict[str, Any])
    @@ -324,7 +324,7 @@

    Class variables

    Static methods

    -def parse(config: ForwardRef('DispatchActionConfig') | Dict[str, Any]) +def parse(config: DispatchActionConfig | Dict[str, Any])
    @@ -446,7 +446,7 @@

    Class variables

    Static methods

    -def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +def parse(feedback_button: FeedbackButtonObject | Dict[str, Any])
    @@ -907,7 +907,7 @@

    Static methods

    Creates a simple Option instance with the same value and label

    -def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None
    @@ -1105,7 +1105,7 @@

    Class variables

    Static methods

    -def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None
    @@ -1536,7 +1536,7 @@

    Class variables

    Static methods

    -def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
    default_type: str = 'mrkdwn') ‑> TextObject | None
    +def parse(text: str | Dict[str, Any] | TextObject,
    default_type: str = 'mrkdwn') ‑> TextObject | None
    diff --git a/docs/reference/models/blocks/block_elements.html b/docs/reference/models/blocks/block_elements.html index 375ee07ce..e79ac10d1 100644 --- a/docs/reference/models/blocks/block_elements.html +++ b/docs/reference/models/blocks/block_elements.html @@ -130,6 +130,7 @@

    Subclasses

  • ImageElement
  • InteractiveElement
  • RichTextElement
  • +
  • UrlSourceElement
  • Class variables

    @@ -145,13 +146,13 @@

    Class variables

    Static methods

    -def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None +def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None
    -def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject]
    @@ -828,7 +829,7 @@

    Class variables

    Static methods

    -def parse(filter: dict | ForwardRef('ConversationFilter')) +def parse(filter: dict | ConversationFilter)
    @@ -3504,7 +3505,7 @@

    Class variables

    class RichTextInputElement -(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    +(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | RichTextBlock | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -4663,6 +4664,103 @@

    Inherited members

    +
    +class UrlSourceElement +(*, url: str, text: str, **others: Dict) +
    +
    +
    + +Expand source code + +
    class UrlSourceElement(BlockElement):
    +    type = "url"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "url",
    +                "text",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str,
    +        text: str,
    +        **others: Dict,
    +    ):
    +        """
    +        A URL source element that displays a URL source for referencing within a task card block.
    +        https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
    +
    +        Args:
    +            url (required): The URL type source.
    +            text (required): Display text for the URL.
    +        """
    +        super().__init__(type=self.type)
    +        show_unknown_key_warning(self, others)
    +        self.url = url
    +        self.text = text
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    A URL source element that displays a URL source for referencing within a task card block. +https://docs.slack.dev/reference/block-kit/block-elements/url-source-element

    +

    Args

    +
    +
    url : required
    +
    The URL type source.
    +
    text : required
    +
    Display text for the URL.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "url",
    +            "text",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class UserMultiSelectElement (*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_users: Sequence[str] | None = None,
    confirm: dict | ConfirmObject | None = None,
    max_selected_items: int | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -5319,6 +5417,13 @@

  • +

    UrlSourceElement

    + +
  • +
  • UserMultiSelectElement

    • attributes
    • diff --git a/docs/reference/models/blocks/blocks.html b/docs/reference/models/blocks/blocks.html index 722d12164..a4f511858 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -238,6 +238,10 @@

      Inherited members

      return RichTextBlock(**block) elif type == TableBlock.type: return TableBlock(**block) + elif type == TaskCardBlock.type: + return TaskCardBlock(**block) + elif type == PlanBlock.type: + return PlanBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -269,9 +273,11 @@

      Subclasses

    • ImageBlock
    • InputBlock
    • MarkdownBlock
    • +
    • PlanBlock
    • RichTextBlock
    • SectionBlock
    • TableBlock
    • +
    • TaskCardBlock
    • VideoBlock

    Class variables

    @@ -292,13 +298,13 @@

    Class variables

    Static methods

    -def parse(block: dict | ForwardRef('Block')) ‑> Block | None +def parse(block: dict | Block) ‑> Block | None
    -def parse_all(blocks: Sequence[dict | ForwardRef('Block')] | None) ‑> List[Block] +def parse_all(blocks: Sequence[dict | Block] | None) ‑> List[Block]
    @@ -1321,6 +1327,115 @@

    Inherited members

  • +
    +class PlanBlock +(*,
    title: str,
    tasks: Sequence[Dict | TaskCardBlock] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class PlanBlock(Block):
    +    type = "plan"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "title",
    +                "tasks",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        title: str,
    +        tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a collection of related tasks.
    +        https://docs.slack.dev/reference/block-kit/blocks/plan-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            title (required): Title of the plan in plain text
    +            tasks: Details of the task in the form of a single "rich_text" entity.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.title = title
    +        self.tasks = tasks
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a collection of related tasks. +https://docs.slack.dev/reference/block-kit/blocks/plan-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    title : required
    +
    Title of the plan in plain text
    +
    tasks
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "title",
    +            "tasks",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class RichTextBlock (*,
    elements: Sequence[dict | RichTextElement],
    block_id: str | None = None,
    **others: dict)
    @@ -1700,6 +1815,147 @@

    Inherited members

    +
    +class TaskCardBlock +(*,
    task_id: str,
    title: str,
    details: RichTextBlock | dict | None = None,
    output: RichTextBlock | dict | None = None,
    sources: Sequence[UrlSourceElement | dict] | None = None,
    status: str,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TaskCardBlock(Block):
    +    type = "task_card"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "task_id",
    +                "title",
    +                "details",
    +                "output",
    +                "sources",
    +                "status",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        task_id: str,
    +        title: str,
    +        details: Optional[Union[RichTextBlock, dict]] = None,
    +        output: Optional[Union[RichTextBlock, dict]] = None,
    +        sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None,
    +        status: str,  # pending, in_progress, complete, error
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a single task, representing a single action.
    +        https://docs.slack.dev/reference/block-kit/blocks/task-card-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            task_id (required): ID for the task
    +            title (required): Title of the task in plain text
    +            details: Details of the task in the form of a single "rich_text" entity.
    +            output: Output of the task in the form of a single "rich_text" entity.
    +            sources: Array of URL source elements used to generate a response.
    +            status: The state of a task. Either "pending", "in_progress", "complete", or "error".
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.task_id = task_id
    +        self.title = title
    +        self.details = details
    +        self.output = output
    +        self.sources = sources
    +        self.status = status
    +
    +    @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)")
    +    def _validate_rows(self):
    +        return self.status in ["pending", "in_progress", "complete", "error"]
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a single task, representing a single action. +https://docs.slack.dev/reference/block-kit/blocks/task-card-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    task_id : required
    +
    ID for the task
    +
    title : required
    +
    Title of the task in plain text
    +
    details
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    output
    +
    Output of the task in the form of a single "rich_text" entity.
    +
    sources
    +
    Array of URL source elements used to generate a response.
    +
    status
    +
    The state of a task. Either "pending", "in_progress", "complete", or "error".
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "task_id",
    +            "title",
    +            "details",
    +            "output",
    +            "sources",
    +            "status",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class VideoBlock (*,
    block_id: str | None = None,
    alt_text: str | None = None,
    video_url: str | None = None,
    thumbnail_url: str | None = None,
    title: str | dict | PlainTextObject | None = None,
    title_url: str | None = None,
    description: str | dict | PlainTextObject | None = None,
    provider_icon_url: str | None = None,
    provider_name: str | None = None,
    author_name: str | None = None,
    **others: dict)
    @@ -2008,6 +2264,13 @@

    PlanBlock

    + + +
  • RichTextBlock

    Class variables

    @@ -451,13 +458,13 @@

    Class variables

    Static methods

    -def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None +def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None
    -def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject]
    @@ -1271,7 +1278,7 @@

    Class variables

    Static methods

    -def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any]) +def parse(confirm: ConfirmObject | Dict[str, Any])
    @@ -1643,7 +1650,7 @@

    Class variables

    Static methods

    -def parse(filter: dict | ForwardRef('ConversationFilter')) +def parse(filter: dict | ConversationFilter)
    @@ -2835,7 +2842,7 @@

    Class variables

    Static methods

    -def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +def parse(feedback_button: FeedbackButtonObject | Dict[str, Any])
    @@ -4720,7 +4727,7 @@

    Static methods

    Creates a simple Option instance with the same value and label

    -def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None
    @@ -4918,7 +4925,7 @@

    Class variables

    Static methods

    -def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None
    @@ -5345,6 +5352,115 @@

    Inherited members

  • +
    +class PlanBlock +(*,
    title: str,
    tasks: Sequence[Dict | TaskCardBlock] | None = None,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class PlanBlock(Block):
    +    type = "plan"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "title",
    +                "tasks",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        title: str,
    +        tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None,
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a collection of related tasks.
    +        https://docs.slack.dev/reference/block-kit/blocks/plan-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            title (required): Title of the plan in plain text
    +            tasks: Details of the task in the form of a single "rich_text" entity.
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.title = title
    +        self.tasks = tasks
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a collection of related tasks. +https://docs.slack.dev/reference/block-kit/blocks/plan-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    title : required
    +
    Title of the plan in plain text
    +
    tasks
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "title",
    +            "tasks",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class RadioButtonsElement (*,
    action_id: str | None = None,
    options: Sequence[dict | Option] | None = None,
    initial_option: dict | Option | None = None,
    confirm: dict | ConfirmObject | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -6027,7 +6143,7 @@

    Class variables

    class RichTextInputElement -(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    +(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | RichTextBlock | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -7196,6 +7312,147 @@

    Inherited members

    +
    +class TaskCardBlock +(*,
    task_id: str,
    title: str,
    details: RichTextBlock | dict | None = None,
    output: RichTextBlock | dict | None = None,
    sources: Sequence[UrlSourceElement | dict] | None = None,
    status: str,
    block_id: str | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class TaskCardBlock(Block):
    +    type = "task_card"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "task_id",
    +                "title",
    +                "details",
    +                "output",
    +                "sources",
    +                "status",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        task_id: str,
    +        title: str,
    +        details: Optional[Union[RichTextBlock, dict]] = None,
    +        output: Optional[Union[RichTextBlock, dict]] = None,
    +        sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None,
    +        status: str,  # pending, in_progress, complete, error
    +        block_id: Optional[str] = None,
    +        **others: dict,
    +    ):
    +        """Displays a single task, representing a single action.
    +        https://docs.slack.dev/reference/block-kit/blocks/task-card-block/
    +
    +        Args:
    +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
    +                Maximum length for this field is 255 characters.
    +                block_id should be unique for each message and each iteration of a message.
    +                If a message is updated, use a new block_id.
    +            task_id (required): ID for the task
    +            title (required): Title of the task in plain text
    +            details: Details of the task in the form of a single "rich_text" entity.
    +            output: Output of the task in the form of a single "rich_text" entity.
    +            sources: Array of URL source elements used to generate a response.
    +            status: The state of a task. Either "pending", "in_progress", "complete", or "error".
    +        """
    +        super().__init__(type=self.type, block_id=block_id)
    +        show_unknown_key_warning(self, others)
    +
    +        self.task_id = task_id
    +        self.title = title
    +        self.details = details
    +        self.output = output
    +        self.sources = sources
    +        self.status = status
    +
    +    @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)")
    +    def _validate_rows(self):
    +        return self.status in ["pending", "in_progress", "complete", "error"]
    +
    +

    Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

    +

    Displays a single task, representing a single action. +https://docs.slack.dev/reference/block-kit/blocks/task-card-block/

    +

    Args

    +
    +
    block_id
    +
    A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
    +
    task_id : required
    +
    ID for the task
    +
    title : required
    +
    Title of the task in plain text
    +
    details
    +
    Details of the task in the form of a single "rich_text" entity.
    +
    output
    +
    Output of the task in the form of a single "rich_text" entity.
    +
    sources
    +
    Array of URL source elements used to generate a response.
    +
    status
    +
    The state of a task. Either "pending", "in_progress", "complete", or "error".
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "task_id",
    +            "title",
    +            "details",
    +            "output",
    +            "sources",
    +            "status",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class TextObject (text: str,
    type: str | None = None,
    subtype: str | None = None,
    emoji: bool | None = None,
    **kwargs)
    @@ -7290,7 +7547,7 @@

    Class variables

    Static methods

    -def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
    default_type: str = 'mrkdwn') ‑> TextObject | None
    +def parse(text: str | Dict[str, Any] | TextObject,
    default_type: str = 'mrkdwn') ‑> TextObject | None
    @@ -7595,6 +7852,103 @@

    Inherited members

    +
    +class UrlSourceElement +(*, url: str, text: str, **others: Dict) +
    +
    +
    + +Expand source code + +
    class UrlSourceElement(BlockElement):
    +    type = "url"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union(
    +            {
    +                "url",
    +                "text",
    +            }
    +        )
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str,
    +        text: str,
    +        **others: Dict,
    +    ):
    +        """
    +        A URL source element that displays a URL source for referencing within a task card block.
    +        https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
    +
    +        Args:
    +            url (required): The URL type source.
    +            text (required): Display text for the URL.
    +        """
    +        super().__init__(type=self.type)
    +        show_unknown_key_warning(self, others)
    +        self.url = url
    +        self.text = text
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    A URL source element that displays a URL source for referencing within a task card block. +https://docs.slack.dev/reference/block-kit/block-elements/url-source-element

    +

    Args

    +
    +
    url : required
    +
    The URL type source.
    +
    text : required
    +
    Display text for the URL.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Instance variables

    +
    +
    prop attributes : Set[str]
    +
    +
    + +Expand source code + +
    @property
    +def attributes(self) -> Set[str]:  # type: ignore[override]
    +    return super().attributes.union(
    +        {
    +            "url",
    +            "text",
    +        }
    +    )
    +
    +

    Build an unordered collection of unique elements.

    +
    +
    +

    Inherited members

    + +
    class UserMultiSelectElement (*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_users: Sequence[str] | None = None,
    confirm: dict | ConfirmObject | None = None,
    max_selected_items: int | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    @@ -8389,6 +8743,13 @@

    PlanBlock

    + + +
  • RadioButtonsElement

    • attributes
    • @@ -8509,6 +8870,13 @@

      TaskCardBlock

      + + +
    • TextObject

      • attributes
      • @@ -8532,6 +8900,13 @@

        UrlSourceElement

        + + +
      • UserMultiSelectElement

        • attributes
        • diff --git a/docs/reference/models/index.html b/docs/reference/models/index.html index d3670bfde..d18523ae7 100644 --- a/docs/reference/models/index.html +++ b/docs/reference/models/index.html @@ -316,6 +316,7 @@

          Subclasses

        • AbstractDialogSelector
        • DialogBuilder
        • DialogTextComponent
        • +
        • Chunk
        • Message
        • ContentItemEntityFields
        • EntityActionButton
        • diff --git a/docs/reference/models/messages/chunk.html b/docs/reference/models/messages/chunk.html new file mode 100644 index 000000000..49c1ef1bc --- /dev/null +++ b/docs/reference/models/messages/chunk.html @@ -0,0 +1,447 @@ + + + + + + +slack_sdk.models.messages.chunk API documentation + + + + + + + + + + + +
          +
          +
          +

          Module slack_sdk.models.messages.chunk

          +
          +
          +
          +
          +
          +
          +
          +
          +
          +
          +

          Classes

          +
          +
          +class Chunk +(*, type: str | None = None) +
          +
          +
          + +Expand source code + +
          class Chunk(JsonObject):
          +    """
          +    Chunk for streaming messages.
          +
          +    https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +    """
          +
          +    attributes = {"type"}
          +    logger = logging.getLogger(__name__)
          +
          +    def __init__(
          +        self,
          +        *,
          +        type: Optional[str] = None,
          +    ):
          +        self.type = type
          +
          +    @classmethod
          +    def parse(cls, chunk: Union[Dict, "Chunk"]) -> Optional["Chunk"]:
          +        if chunk is None:
          +            return None
          +        elif isinstance(chunk, Chunk):
          +            return chunk
          +        else:
          +            if "type" in chunk:
          +                type = chunk["type"]
          +                if type == MarkdownTextChunk.type:
          +                    return MarkdownTextChunk(**chunk)
          +                elif type == PlanUpdateChunk.type:
          +                    return PlanUpdateChunk(**chunk)
          +                elif type == TaskUpdateChunk.type:
          +                    return TaskUpdateChunk(**chunk)
          +                else:
          +                    cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
          +                    return None
          +            else:
          +                cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})")
          +                return None
          +
          + +

          Ancestors

          + +

          Subclasses

          + +

          Class variables

          +
          +
          var attributes
          +
          +

          The type of the None singleton.

          +
          +
          var logger
          +
          +

          The type of the None singleton.

          +
          +
          +

          Static methods

          +
          +
          +def parse(chunk: Dict | Chunk) ‑> Chunk | None +
          +
          +
          +
          +
          +

          Inherited members

          + +
          +
          +class MarkdownTextChunk +(*, text: str, **others: Dict) +
          +
          +
          + +Expand source code + +
          class MarkdownTextChunk(Chunk):
          +    type = "markdown_text"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"text"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        text: str,
          +        **others: Dict,
          +    ):
          +        """Used for streaming text content with markdown formatting support.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.text = text
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union({"text"})
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +class PlanUpdateChunk +(*, title: str, **others: Dict) +
          +
          +
          + +Expand source code + +
          class PlanUpdateChunk(Chunk):
          +    type = "plan_update"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"title"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        title: str,
          +        **others: Dict,
          +    ):
          +        """Used for displaying an updated title of a plan.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.title = title
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union({"title"})
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +class TaskUpdateChunk +(*,
          id: str,
          title: str,
          status: str,
          details: str | None = None,
          output: str | None = None,
          sources: Sequence[Dict | UrlSourceElement] | None = None,
          **others: Dict)
          +
          +
          +
          + +Expand source code + +
          class TaskUpdateChunk(Chunk):
          +    type = "task_update"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union(
          +            {
          +                "id",
          +                "title",
          +                "status",
          +                "details",
          +                "output",
          +                "sources",
          +            }
          +        )
          +
          +    def __init__(
          +        self,
          +        *,
          +        id: str,
          +        title: str,
          +        status: str,  # "pending", "in_progress", "complete", "error"
          +        details: Optional[str] = None,
          +        output: Optional[str] = None,
          +        sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None,
          +        **others: Dict,
          +    ):
          +        """Used for displaying task progress in a timeline-style UI.
          +
          +        https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming
          +        """
          +        super().__init__(type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.id = id
          +        self.title = title
          +        self.status = status
          +        self.details = details
          +        self.output = output
          +        self.sources = sources
          +
          + +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union(
          +        {
          +            "id",
          +            "title",
          +            "status",
          +            "details",
          +            "output",
          +            "sources",
          +        }
          +    )
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          +
          +
          +
          + +
          + + + diff --git a/docs/reference/models/messages/index.html b/docs/reference/models/messages/index.html index 5d10ced1c..3139098be 100644 --- a/docs/reference/models/messages/index.html +++ b/docs/reference/models/messages/index.html @@ -40,6 +40,10 @@

          Module slack_sdk.models.messages

          Sub-modules

          +
          slack_sdk.models.messages.chunk
          +
          +
          +
          slack_sdk.models.messages.message
          @@ -262,6 +266,7 @@

          Class variables

        • Sub-modules

        • diff --git a/docs/reference/models/views/index.html b/docs/reference/models/views/index.html index d9e60359b..b7bad66bc 100644 --- a/docs/reference/models/views/index.html +++ b/docs/reference/models/views/index.html @@ -48,7 +48,7 @@

          Classes

          class View -(type: str,
          id: str | None = None,
          callback_id: str | None = None,
          external_id: str | None = None,
          team_id: str | None = None,
          bot_id: str | None = None,
          app_id: str | None = None,
          root_view_id: str | None = None,
          previous_view_id: str | None = None,
          title: str | dict | PlainTextObject | None = None,
          submit: str | dict | PlainTextObject | None = None,
          close: str | dict | PlainTextObject | None = None,
          blocks: Sequence[dict | Block] | None = None,
          private_metadata: str | None = None,
          state: dict | ForwardRef('ViewState') | None = None,
          hash: str | None = None,
          clear_on_close: bool | None = None,
          notify_on_close: bool | None = None,
          **kwargs)
          +(type: str,
          id: str | None = None,
          callback_id: str | None = None,
          external_id: str | None = None,
          team_id: str | None = None,
          bot_id: str | None = None,
          app_id: str | None = None,
          root_view_id: str | None = None,
          previous_view_id: str | None = None,
          title: str | dict | PlainTextObject | None = None,
          submit: str | dict | PlainTextObject | None = None,
          close: str | dict | PlainTextObject | None = None,
          blocks: Sequence[dict | Block] | None = None,
          private_metadata: str | None = None,
          state: dict | ViewState | None = None,
          hash: str | None = None,
          clear_on_close: bool | None = None,
          notify_on_close: bool | None = None,
          **kwargs)
          @@ -229,7 +229,7 @@

          Inherited members

          class ViewState -(*,
          values: Dict[str, Dict[str, dict | ForwardRef('ViewStateValue')]])
          +(*,
          values: Dict[str, Dict[str, dict | ViewStateValue]])
          diff --git a/docs/reference/oauth/installation_store/index.html b/docs/reference/oauth/installation_store/index.html index 4f90fb1c5..8d95802d0 100644 --- a/docs/reference/oauth/installation_store/index.html +++ b/docs/reference/oauth/installation_store/index.html @@ -193,10 +193,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: @@ -850,14 +852,18 @@

          Inherited members

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -865,7 +871,7 @@

          Inherited members

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/bot.html b/docs/reference/oauth/installation_store/models/bot.html index bb6182df2..b27abfc7d 100644 --- a/docs/reference/oauth/installation_store/models/bot.html +++ b/docs/reference/oauth/installation_store/models/bot.html @@ -150,10 +150,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/index.html b/docs/reference/oauth/installation_store/models/index.html index b96d16179..69e08affe 100644 --- a/docs/reference/oauth/installation_store/models/index.html +++ b/docs/reference/oauth/installation_store/models/index.html @@ -161,10 +161,12 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "is_enterprise_install": self.is_enterprise_install, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: @@ -469,14 +471,18 @@

          Methods

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -484,7 +490,7 @@

          Methods

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/installation_store/models/installation.html b/docs/reference/oauth/installation_store/models/installation.html index c0de92807..3f47d901a 100644 --- a/docs/reference/oauth/installation_store/models/installation.html +++ b/docs/reference/oauth/installation_store/models/installation.html @@ -222,14 +222,18 @@

          Classes

          "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None, "bot_refresh_token": self.bot_refresh_token, "bot_token_expires_at": ( - datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None + datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc) + if self.bot_token_expires_at is not None + else None ), "user_id": self.user_id, "user_token": self.user_token, "user_scopes": ",".join(self.user_scopes) if self.user_scopes else None, "user_refresh_token": self.user_refresh_token, "user_token_expires_at": ( - datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None + datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc) + if self.user_token_expires_at is not None + else None ), "incoming_webhook_url": self.incoming_webhook_url, "incoming_webhook_channel": self.incoming_webhook_channel, @@ -237,7 +241,7 @@

          Classes

          "incoming_webhook_configuration_url": self.incoming_webhook_configuration_url, "is_enterprise_install": self.is_enterprise_install, "token_type": self.token_type, - "installed_at": datetime.utcfromtimestamp(self.installed_at), + "installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc), } def to_dict_for_copying(self) -> Dict[str, Any]: diff --git a/docs/reference/oauth/state_store/sqlalchemy/index.html b/docs/reference/oauth/state_store/sqlalchemy/index.html index 29fe36884..a35fa3171 100644 --- a/docs/reference/oauth/state_store/sqlalchemy/index.html +++ b/docs/reference/oauth/state_store/sqlalchemy/index.html @@ -99,7 +99,7 @@

          Classes

          async def async_issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) async with self.engine.begin() as conn: await conn.execute( self.oauth_states.insert(), @@ -111,7 +111,7 @@

          Classes

          try: async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -191,7 +191,7 @@

          Methods

          try: async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -215,7 +215,7 @@

          Methods

          async def async_issue(self, *args, **kwargs) -> str:
               state: str = str(uuid4())
          -    now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
          +    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
               async with self.engine.begin() as conn:
                   await conn.execute(
                       self.oauth_states.insert(),
          @@ -293,7 +293,7 @@ 

          Methods

          def issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds) + now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) with self.engine.begin() as conn: conn.execute( self.oauth_states.insert(), @@ -305,7 +305,7 @@

          Methods

          try: with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -385,7 +385,7 @@

          Methods

          try: with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow())) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -422,7 +422,7 @@

          Methods

          def issue(self, *args, **kwargs) -> str:
               state: str = str(uuid4())
          -    now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
          +    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
               with self.engine.begin() as conn:
                   conn.execute(
                       self.oauth_states.insert(),
          diff --git a/docs/reference/web/async_chat_stream.html b/docs/reference/web/async_chat_stream.html
          index ca7bf2506..d8e4f5bfe 100644
          --- a/docs/reference/web/async_chat_stream.html
          +++ b/docs/reference/web/async_chat_stream.html
          @@ -48,7 +48,7 @@ 

          Classes

          class AsyncChatStream -(client: AsyncWebClient,
          *,
          channel: str,
          logger: logging.Logger,
          thread_ts: str,
          buffer_size: int,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs)
          +(client: AsyncWebClient,
          *,
          channel: str,
          logger: logging.Logger,
          thread_ts: str,
          buffer_size: int,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          task_display_mode: str | None = None,
          **kwargs)
          @@ -72,6 +72,7 @@

          Classes

          buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -87,6 +88,8 @@

          Classes

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -99,6 +102,7 @@

          Classes

          "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -109,7 +113,8 @@

          Classes

          async def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[AsyncSlackResponse]: """Append to the stream. @@ -118,6 +123,7 @@

          Classes

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -145,9 +151,10 @@

          Classes

          raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return await self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -163,6 +170,7 @@

          Classes

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -171,6 +179,7 @@

          Classes

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -211,26 +220,36 @@

          Classes

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + async def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = await self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -240,7 +259,7 @@

          Classes

          channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response
          @@ -266,6 +285,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          buffer_size
          The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
          @@ -275,7 +297,7 @@

          Args

          Methods

          -async def append(self, *, markdown_text: str, **kwargs) ‑> AsyncSlackResponse | None +async def append(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse | None
          @@ -285,7 +307,8 @@

          Methods

          async def append(
               self,
               *,
          -    markdown_text: str,
          +    markdown_text: Optional[str] = None,
          +    chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
               **kwargs,
           ) -> Optional[AsyncSlackResponse]:
               """Append to the stream.
          @@ -294,6 +317,7 @@ 

          Methods

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -321,9 +345,10 @@

          Methods

          raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return await self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -340,6 +365,8 @@

          Methods

          is stopped this method cannot be called.

          Args

          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          markdown_text
          Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far.
          @@ -366,7 +393,7 @@

          Example

          -async def stop(self,
          *,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def stop(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -377,6 +404,7 @@

          Example

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -385,6 +413,7 @@

          Example

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -425,12 +454,17 @@

          Example

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) @@ -442,6 +476,8 @@

          Args

          blocks
          A list of blocks that will be rendered at the bottom of the finalized message.
          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          markdown_text
          Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far.
          diff --git a/docs/reference/web/async_client.html b/docs/reference/web/async_client.html index 2ec1ba84a..070de0e95 100644 --- a/docs/reference/web/async_client.html +++ b/docs/reference/web/async_client.html @@ -2651,7 +2651,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -2662,8 +2663,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs) @@ -2905,6 +2908,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -2917,8 +2922,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs) @@ -2930,6 +2938,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -2942,6 +2951,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2956,6 +2966,7 @@

          Classes

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -2982,6 +2993,8 @@

          Classes

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3007,6 +3020,7 @@

          Classes

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10144,7 +10158,7 @@

          Methods

          Unarchives a channel.

          -async def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> AsyncSlackResponse +async def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10156,7 +10170,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -10167,8 +10182,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs)
          @@ -10494,7 +10511,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -async def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10509,6 +10526,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -10521,8 +10540,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs)
          @@ -10530,7 +10552,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -async def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> AsyncSlackResponse
          +async def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> AsyncSlackResponse
          @@ -10545,6 +10567,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -10557,6 +10580,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10567,7 +10591,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.stopStream

          -async def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> AsyncChatStream
          +async def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> AsyncChatStream
          @@ -10582,6 +10606,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -10608,6 +10633,8 @@

          Methods

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10633,6 +10660,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, )
          @@ -10665,6 +10693,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/chat_stream.html b/docs/reference/web/chat_stream.html index 94d96e5eb..59fcbbd84 100644 --- a/docs/reference/web/chat_stream.html +++ b/docs/reference/web/chat_stream.html @@ -48,7 +48,7 @@

          Classes

          class ChatStream -(client: WebClient,
          *,
          channel: str,
          logger: logging.Logger,
          thread_ts: str,
          buffer_size: int,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs)
          +(client: WebClient,
          *,
          channel: str,
          logger: logging.Logger,
          thread_ts: str,
          buffer_size: int,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          task_display_mode: str | None = None,
          **kwargs)
          @@ -72,6 +72,7 @@

          Classes

          buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -87,6 +88,8 @@

          Classes

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -99,6 +102,7 @@

          Classes

          "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -109,7 +113,8 @@

          Classes

          def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[SlackResponse]: """Append to the stream. @@ -118,6 +123,7 @@

          Classes

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -145,9 +151,10 @@

          Classes

          raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -163,6 +170,7 @@

          Classes

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -171,6 +179,7 @@

          Classes

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -211,26 +220,36 @@

          Classes

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - def _flush_buffer(self, **kwargs) -> SlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> SlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -240,7 +259,7 @@

          Classes

          channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response @@ -266,6 +285,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          buffer_size
          The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits.
          @@ -275,7 +297,7 @@

          Args

          Methods

          -def append(self, *, markdown_text: str, **kwargs) ‑> SlackResponse | None +def append(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse | None
          @@ -285,7 +307,8 @@

          Methods

          def append(
               self,
               *,
          -    markdown_text: str,
          +    markdown_text: Optional[str] = None,
          +    chunks: Optional[Sequence[Union[Dict, Chunk]]] = None,
               **kwargs,
           ) -> Optional[SlackResponse]:
               """Append to the stream.
          @@ -294,6 +317,7 @@ 

          Methods

          is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -321,9 +345,10 @@

          Methods

          raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -340,6 +365,8 @@

          Methods

          is stopped this method cannot be called.

          Args

          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          markdown_text
          Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far.
          @@ -366,7 +393,7 @@

          Example

          -def stop(self,
          *,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def stop(self,
          *,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          @@ -377,6 +404,7 @@

          Example

          self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -385,6 +413,7 @@

          Example

          Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -425,12 +454,17 @@

          Example

          raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) @@ -442,6 +476,8 @@

          Args

          blocks
          A list of blocks that will be rendered at the bottom of the finalized message.
          +
          chunks
          +
          An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks.
          markdown_text
          Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far.
          diff --git a/docs/reference/web/client.html b/docs/reference/web/client.html index 6fded11e1..fecd86b7c 100644 --- a/docs/reference/web/client.html +++ b/docs/reference/web/client.html @@ -2651,7 +2651,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2662,8 +2663,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2905,6 +2908,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -2917,8 +2922,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2930,6 +2938,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -2942,6 +2951,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2956,6 +2966,7 @@

          Classes

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -2982,6 +2993,8 @@

          Classes

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3007,6 +3020,7 @@

          Classes

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10144,7 +10158,7 @@

          Methods

          Unarchives a channel.

          -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10156,7 +10170,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10167,8 +10182,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
          @@ -10494,7 +10511,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10509,6 +10526,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10521,8 +10540,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
          @@ -10530,7 +10552,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10545,6 +10567,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10557,6 +10580,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10567,7 +10591,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.stopStream

          -def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> ChatStream
          +def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> ChatStream
          @@ -10582,6 +10606,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10608,6 +10633,8 @@

          Methods

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -10633,6 +10660,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10665,6 +10693,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/index.html b/docs/reference/web/index.html index 611a26b3b..bec63590c 100644 --- a/docs/reference/web/index.html +++ b/docs/reference/web/index.html @@ -3020,7 +3020,8 @@

          Raises

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -3031,8 +3032,10 @@

          Raises

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -3274,6 +3277,8 @@

          Raises

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -3286,8 +3291,11 @@

          Raises

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -3299,6 +3307,7 @@

          Raises

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -3311,6 +3320,7 @@

          Raises

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -3325,6 +3335,7 @@

          Raises

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -3351,6 +3362,8 @@

          Raises

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3376,6 +3389,7 @@

          Raises

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -10513,7 +10527,7 @@

          Methods

          Unarchives a channel.

          -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +def chat_appendStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10525,7 +10539,8 @@

          Methods

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -10536,8 +10551,10 @@

          Methods

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
          @@ -10863,7 +10880,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

          -def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_startStream(self,
          *,
          channel: str,
          thread_ts: str,
          markdown_text: str | None = None,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10878,6 +10895,8 @@

          Methods

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -10890,8 +10909,11 @@

          Methods

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
          @@ -10899,7 +10921,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.startStream

          -def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          **kwargs) ‑> SlackResponse
          +def chat_stopStream(self,
          *,
          channel: str,
          ts: str,
          markdown_text: str | None = None,
          blocks: str | Sequence[Dict | Block] | None = None,
          metadata: Dict | Metadata | None = None,
          chunks: Sequence[Dict | Chunk] | None = None,
          **kwargs) ‑> SlackResponse
          @@ -10914,6 +10936,7 @@

          Methods

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -10926,6 +10949,7 @@

          Methods

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10936,7 +10960,7 @@

          Methods

          https://docs.slack.dev/reference/methods/chat.stopStream

          -def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          **kwargs) ‑> ChatStream
          +def chat_stream(self,
          *,
          buffer_size: int = 256,
          channel: str,
          thread_ts: str,
          recipient_team_id: str | None = None,
          recipient_user_id: str | None = None,
          task_display_mode: str | None = None,
          **kwargs) ‑> ChatStream
          @@ -10951,6 +10975,7 @@

          Methods

          thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -10977,6 +11002,8 @@

          Methods

          recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -11002,6 +11029,7 @@

          Methods

          thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) @@ -11034,6 +11062,9 @@

          Args

          streaming to channels.
          recipient_user_id
          The encoded ID of the user to receive the streaming text. Required when streaming to channels.
          +
          task_display_mode
          +
          Specifies how tasks are displayed in the message. A "timeline" displays individual tasks +with text and "plan" displays all tasks together.
          **kwargs
          Additional arguments passed to the underlying API calls.
          diff --git a/docs/reference/web/legacy_client.html b/docs/reference/web/legacy_client.html index d100178dc..86d9b1d0c 100644 --- a/docs/reference/web/legacy_client.html +++ b/docs/reference/web/legacy_client.html @@ -2650,7 +2650,8 @@

          Classes

          *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -2661,8 +2662,10 @@

          Classes

          "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2904,6 +2907,8 @@

          Classes

          markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -2916,8 +2921,11 @@

          Classes

          "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2929,6 +2937,7 @@

          Classes

          markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -2941,6 +2950,7 @@

          Classes

          "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10080,7 +10090,7 @@

          Methods

          Unarchives a channel.

  • -def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +def chat_appendStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10092,7 +10102,8 @@

    Methods

    *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -10103,8 +10114,10 @@

    Methods

    "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs)
    @@ -10430,7 +10443,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

    -def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_startStream(self,
    *,
    channel: str,
    thread_ts: str,
    markdown_text: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    task_display_mode: str | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10445,6 +10458,8 @@

    Methods

    markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -10457,8 +10472,11 @@

    Methods

    "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs)
    @@ -10466,7 +10484,7 @@

    Methods

    https://docs.slack.dev/reference/methods/chat.startStream

    -def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    +def chat_stopStream(self,
    *,
    channel: str,
    ts: str,
    markdown_text: str | None = None,
    blocks: str | Sequence[Dict | Block] | None = None,
    metadata: Dict | Metadata | None = None,
    chunks: Sequence[Dict | Chunk] | None = None,
    **kwargs) ‑> _asyncio.Future | LegacySlackResponse
    @@ -10481,6 +10499,7 @@

    Methods

    markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -10493,6 +10512,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index d2776a9dc..b8592c2e9 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -55,6 +55,7 @@ StaticSelectElement, TimePickerElement, UrlInputElement, + UrlSourceElement, UserMultiSelectElement, UserSelectElement, ) @@ -70,9 +71,11 @@ ImageBlock, InputBlock, MarkdownBlock, + PlanBlock, RichTextBlock, SectionBlock, TableBlock, + TaskCardBlock, VideoBlock, ) @@ -111,6 +114,7 @@ "PlainTextInputElement", "EmailInputElement", "UrlInputElement", + "UrlSourceElement", "NumberInputElement", "RadioButtonsElement", "SelectElement", @@ -135,8 +139,10 @@ "ImageBlock", "InputBlock", "MarkdownBlock", + "PlanBlock", "SectionBlock", "TableBlock", + "TaskCardBlock", "VideoBlock", "RichTextBlock", ] diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py index 89f0a7994..bcf7adfe0 100644 --- a/slack_sdk/models/blocks/block_elements.py +++ b/slack_sdk/models/blocks/block_elements.py @@ -1654,6 +1654,44 @@ def __init__( self.dispatch_action_config = dispatch_action_config +# ------------------------------------------------- +# Url Source Element +# ------------------------------------------------- + + +class UrlSourceElement(BlockElement): + type = "url" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "url", + "text", + } + ) + + def __init__( + self, + *, + url: str, + text: str, + **others: Dict, + ): + """ + A URL source element that displays a URL source for referencing within a task card block. + https://docs.slack.dev/reference/block-kit/block-elements/url-source-element + + Args: + url (required): The URL type source. + text (required): Display text for the URL. + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + self.url = url + self.text = text + + # ------------------------------------------------- # Number Input Element # ------------------------------------------------- diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index cac463c99..bcd7efd6e 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -16,6 +16,7 @@ InputInteractiveElement, InteractiveElement, RichTextElement, + UrlSourceElement, ) # ------------------------------------------------- @@ -97,6 +98,10 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]: return RichTextBlock(**block) elif type == TableBlock.type: return TableBlock(**block) + elif type == TaskCardBlock.type: + return TaskCardBlock(**block) + elif type == PlanBlock.type: + return PlanBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -777,3 +782,99 @@ def __init__( @JsonValidator("rows attribute must be specified") def _validate_rows(self): return self.rows is not None and len(self.rows) > 0 + + +class TaskCardBlock(Block): + type = "task_card" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "task_id", + "title", + "details", + "output", + "sources", + "status", + } + ) + + def __init__( + self, + *, + task_id: str, + title: str, + details: Optional[Union[RichTextBlock, dict]] = None, + output: Optional[Union[RichTextBlock, dict]] = None, + sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None, + status: str, # pending, in_progress, complete, error + block_id: Optional[str] = None, + **others: dict, + ): + """Displays a single task, representing a single action. + https://docs.slack.dev/reference/block-kit/blocks/task-card-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + task_id (required): ID for the task + title (required): Title of the task in plain text + details: Details of the task in the form of a single "rich_text" entity. + output: Output of the task in the form of a single "rich_text" entity. + sources: Array of URL source elements used to generate a response. + status: The state of a task. Either "pending", "in_progress", "complete", or "error". + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.task_id = task_id + self.title = title + self.details = details + self.output = output + self.sources = sources + self.status = status + + @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)") + def _validate_rows(self): + return self.status in ["pending", "in_progress", "complete", "error"] + + +class PlanBlock(Block): + type = "plan" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "title", + "tasks", + } + ) + + def __init__( + self, + *, + title: str, + tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None, + block_id: Optional[str] = None, + **others: dict, + ): + """Displays a collection of related tasks. + https://docs.slack.dev/reference/block-kit/blocks/plan-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + title (required): Title of the plan in plain text + tasks: A sequence of task card blocks. Each task represents a single action within the plan. + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.title = title + self.tasks = tasks diff --git a/slack_sdk/models/messages/chunk.py b/slack_sdk/models/messages/chunk.py new file mode 100644 index 000000000..657d95ae4 --- /dev/null +++ b/slack_sdk/models/messages/chunk.py @@ -0,0 +1,134 @@ +import logging +from typing import Dict, Optional, Sequence, Set, Union + +from slack_sdk.models import show_unknown_key_warning +from slack_sdk.models.basic_objects import JsonObject +from slack_sdk.models.blocks.block_elements import UrlSourceElement + + +class Chunk(JsonObject): + """ + Chunk for streaming messages. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + + attributes = {"type"} + logger = logging.getLogger(__name__) + + def __init__( + self, + *, + type: Optional[str] = None, + ): + self.type = type + + @classmethod + def parse(cls, chunk: Union[Dict, "Chunk"]) -> Optional["Chunk"]: + if chunk is None: + return None + elif isinstance(chunk, Chunk): + return chunk + else: + if "type" in chunk: + type = chunk["type"] + if type == MarkdownTextChunk.type: + return MarkdownTextChunk(**chunk) + elif type == PlanUpdateChunk.type: + return PlanUpdateChunk(**chunk) + elif type == TaskUpdateChunk.type: + return TaskUpdateChunk(**chunk) + else: + cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})") + return None + else: + cls.logger.warning(f"Unknown chunk detected and skipped ({chunk})") + return None + + +class MarkdownTextChunk(Chunk): + type = "markdown_text" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"text"}) + + def __init__( + self, + *, + text: str, + **others: Dict, + ): + """Used for streaming text content with markdown formatting support. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.text = text + + +class PlanUpdateChunk(Chunk): + type = "plan_update" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"title"}) + + def __init__( + self, + *, + title: str, + **others: Dict, + ): + """Used for displaying an updated title of a plan. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.title = title + + +class TaskUpdateChunk(Chunk): + type = "task_update" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "id", + "title", + "status", + "details", + "output", + "sources", + } + ) + + def __init__( + self, + *, + id: str, + title: str, + status: str, # "pending", "in_progress", "complete", "error" + details: Optional[str] = None, + output: Optional[str] = None, + sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None, + **others: Dict, + ): + """Used for displaying task progress in a timeline-style UI. + + https://docs.slack.dev/messaging/sending-and-scheduling-messages#text-streaming + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + + self.id = id + self.title = title + self.status = status + self.details = details + self.output = output + self.sources = sources diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py index 4661f19dd..7348b90bc 100644 --- a/slack_sdk/web/async_chat_stream.py +++ b/slack_sdk/web/async_chat_stream.py @@ -10,10 +10,11 @@ import json import logging -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.messages.chunk import Chunk, MarkdownTextChunk from slack_sdk.models.metadata import Metadata from slack_sdk.web.async_slack_response import AsyncSlackResponse @@ -38,6 +39,7 @@ def __init__( buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -53,6 +55,8 @@ def __init__( recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -65,6 +69,7 @@ def __init__( "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -75,7 +80,8 @@ def __init__( async def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[AsyncSlackResponse]: """Append to the stream. @@ -84,6 +90,7 @@ async def append( is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -111,9 +118,10 @@ async def append( raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return await self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return await self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -129,6 +137,7 @@ async def stop( self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -137,6 +146,7 @@ async def stop( Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -177,26 +187,36 @@ async def stop( raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = await self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + async def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = await self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -206,7 +226,7 @@ async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index ca163da98..e96a4f6d5 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -17,12 +17,13 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk from slack_sdk.models.views import View from slack_sdk.web.async_chat_stream import AsyncChatStream from ..models.attachments import Attachment from ..models.blocks import Block, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .async_base_client import AsyncBaseClient, AsyncSlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2630,7 +2631,8 @@ async def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Appends text to an existing streaming conversation. @@ -2641,8 +2643,10 @@ async def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.appendStream", json=kwargs) @@ -2884,6 +2888,8 @@ async def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -2896,8 +2902,11 @@ async def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return await self.api_call("chat.startStream", json=kwargs) @@ -2909,6 +2918,7 @@ async def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> AsyncSlackResponse: """Stops a streaming conversation. @@ -2921,6 +2931,7 @@ async def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2935,6 +2946,7 @@ async def chat_stream( thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -2961,6 +2973,8 @@ async def chat_stream( recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -2986,6 +3000,7 @@ async def chat_stream( thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py index 1a379c9cb..683859490 100644 --- a/slack_sdk/web/chat_stream.py +++ b/slack_sdk/web/chat_stream.py @@ -1,9 +1,10 @@ import json import logging -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.messages.chunk import Chunk, MarkdownTextChunk from slack_sdk.models.metadata import Metadata from slack_sdk.web.slack_response import SlackResponse @@ -28,6 +29,7 @@ def __init__( buffer_size: int, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -43,6 +45,8 @@ def __init__( recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -55,6 +59,7 @@ def __init__( "thread_ts": thread_ts, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "task_display_mode": task_display_mode, **kwargs, } self._buffer = "" @@ -65,7 +70,8 @@ def __init__( def append( self, *, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Optional[SlackResponse]: """Append to the stream. @@ -74,6 +80,7 @@ def append( is stopped this method cannot be called. Args: + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. **kwargs: Additional arguments passed to the underlying API calls. @@ -101,9 +108,10 @@ def append( raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") if kwargs.get("token"): self._token = kwargs.pop("token") - self._buffer += markdown_text - if len(self._buffer) >= self._buffer_size: - return self._flush_buffer(**kwargs) + if markdown_text is not None: + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size or chunks is not None: + return self._flush_buffer(chunks=chunks, **kwargs) details = { "buffer_length": len(self._buffer), "buffer_size": self._buffer_size, @@ -119,6 +127,7 @@ def stop( self, *, markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, **kwargs, @@ -127,6 +136,7 @@ def stop( Args: blocks: A list of blocks that will be rendered at the bottom of the finalized message. + chunks: An array of streaming chunks. Chunks can be markdown text, plan, or task update chunks. markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is what will be appended to the message received so far. metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you @@ -167,26 +177,36 @@ def stop( raise e.SlackRequestError("Failed to stop stream: stream not started") self._stream_ts = str(response["ts"]) self._state = "in_progress" + flushings: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + flushings.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + flushings.extend(chunks) response = self._client.chat_stopStream( token=self._token, channel=self._stream_args["channel"], ts=self._stream_ts, blocks=blocks, - markdown_text=self._buffer, + chunks=flushings, metadata=metadata, **kwargs, ) self._state = "completed" return response - def _flush_buffer(self, **kwargs) -> SlackResponse: - """Flush the internal buffer by making appropriate API calls.""" + def _flush_buffer(self, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs) -> SlackResponse: + """Flush the internal buffer with chunks by making appropriate API calls.""" + chunks_to_flush: List[Union[Dict, Chunk]] = [] + if len(self._buffer) != 0: + chunks_to_flush.append(MarkdownTextChunk(text=self._buffer)) + if chunks is not None: + chunks_to_flush.extend(chunks) if not self._stream_ts: response = self._client.chat_startStream( **self._stream_args, token=self._token, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._stream_ts = response.get("ts") self._state = "in_progress" @@ -196,7 +216,7 @@ def _flush_buffer(self, **kwargs) -> SlackResponse: channel=self._stream_args["channel"], ts=self._stream_ts, **kwargs, - markdown_text=self._buffer, + chunks=chunks_to_flush, ) self._buffer = "" return response diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index dfa771832..200b216ff 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -7,12 +7,13 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk from slack_sdk.models.views import View from slack_sdk.web.chat_stream import ChatStream from ..models.attachments import Attachment from ..models.blocks import Block, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .base_client import BaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2620,7 +2621,8 @@ def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Appends text to an existing streaming conversation. @@ -2631,8 +2633,10 @@ def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2874,6 +2878,8 @@ def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -2886,8 +2892,11 @@ def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2899,6 +2908,7 @@ def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> SlackResponse: """Stops a streaming conversation. @@ -2911,6 +2921,7 @@ def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -2925,6 +2936,7 @@ def chat_stream( thread_ts: str, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + task_display_mode: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -2951,6 +2963,8 @@ def chat_stream( recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when streaming to channels. recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks + with text and "plan" displays all tasks together. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -2976,6 +2990,7 @@ def chat_stream( thread_ts=thread_ts, recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, + task_display_mode=task_display_mode, buffer_size=buffer_size, **kwargs, ) diff --git a/slack_sdk/web/internal_utils.py b/slack_sdk/web/internal_utils.py index 87139559c..ad23f87f8 100644 --- a/slack_sdk/web/internal_utils.py +++ b/slack_sdk/web/internal_utils.py @@ -11,13 +11,14 @@ from ssl import SSLContext from typing import Any, Dict, Optional, Sequence, Union from urllib.parse import urljoin -from urllib.request import OpenerDirector, ProxyHandler, HTTPSHandler, Request, urlopen +from urllib.request import HTTPSHandler, OpenerDirector, ProxyHandler, Request, urlopen from slack_sdk import version from slack_sdk.errors import SlackRequestError from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block -from slack_sdk.models.metadata import Metadata, EventAndEntityMetadata, EntityMetadata +from slack_sdk.models.messages.chunk import Chunk +from slack_sdk.models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata def convert_bool_to_0_or_1(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: @@ -187,11 +188,13 @@ def _build_req_args( def _parse_web_class_objects(kwargs) -> None: - def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata, EntityMetadata]): + def to_dict(obj: Union[Dict, Block, Attachment, Chunk, Metadata, EventAndEntityMetadata, EntityMetadata]): if isinstance(obj, Block): return obj.to_dict() if isinstance(obj, Attachment): return obj.to_dict() + if isinstance(obj, Chunk): + return obj.to_dict() if isinstance(obj, Metadata): return obj.to_dict() if isinstance(obj, EventAndEntityMetadata): @@ -211,6 +214,11 @@ def to_dict(obj: Union[Dict, Block, Attachment, Metadata, EventAndEntityMetadata dict_attachments = [to_dict(a) for a in attachments] kwargs.update({"attachments": dict_attachments}) + chunks = kwargs.get("chunks", None) + if chunks is not None and isinstance(chunks, Sequence) and (not isinstance(chunks, str)): + dict_chunks = [to_dict(c) for c in chunks] + kwargs.update({"chunks": dict_chunks}) + metadata = kwargs.get("metadata", None) if metadata is not None and ( isinstance(metadata, Metadata) diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index df2bcc370..061be7c85 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -19,11 +19,12 @@ from typing import Any, Dict, List, Optional, Sequence, Union import slack_sdk.errors as e +from slack_sdk.models.messages.chunk import Chunk from slack_sdk.models.views import View from ..models.attachments import Attachment from ..models.blocks import Block, RichTextBlock -from ..models.metadata import Metadata, EntityMetadata, EventAndEntityMetadata +from ..models.metadata import EntityMetadata, EventAndEntityMetadata, Metadata from .legacy_base_client import LegacyBaseClient, SlackResponse from .internal_utils import ( _parse_web_class_objects, @@ -2631,7 +2632,8 @@ def chat_appendStream( *, channel: str, ts: str, - markdown_text: str, + markdown_text: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Appends text to an existing streaming conversation. @@ -2642,8 +2644,10 @@ def chat_appendStream( "channel": channel, "ts": ts, "markdown_text": markdown_text, + "chunks": chunks, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.appendStream", json=kwargs) @@ -2885,6 +2889,8 @@ def chat_startStream( markdown_text: Optional[str] = None, recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, + task_display_mode: Optional[str] = None, # timeline, plan **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -2897,8 +2903,11 @@ def chat_startStream( "markdown_text": markdown_text, "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, + "chunks": chunks, + "task_display_mode": task_display_mode, } ) + _parse_web_class_objects(kwargs) kwargs = _remove_none_values(kwargs) return self.api_call("chat.startStream", json=kwargs) @@ -2910,6 +2919,7 @@ def chat_stopStream( markdown_text: Optional[str] = None, blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, metadata: Optional[Union[Dict, Metadata]] = None, + chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Stops a streaming conversation. @@ -2922,6 +2932,7 @@ def chat_stopStream( "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index 6f3b9f141..531ebe057 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -21,6 +21,7 @@ Option, OverflowMenuElement, PlainTextObject, + PlanBlock, RawTextObject, RichTextBlock, RichTextElementParts, @@ -31,6 +32,7 @@ SectionBlock, StaticSelectElement, TableBlock, + TaskCardBlock, VideoBlock, ) from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile @@ -890,6 +892,86 @@ def test_text_length_12001(self): MarkdownBlock(**input).validate_json() +# ---------------------------------------------- +# Plan +# ---------------------------------------------- + + +class PlanBlockTests(unittest.TestCase): + def test_document(self): + input = { + "type": "plan", + "title": "Thinking completed", + "tasks": [ + { + "task_id": "call_001", + "title": "Fetched user profile information", + "status": "in_progress", + "details": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "Searched database..."}]} + ], + }, + "output": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "Profile data loaded"}]} + ], + }, + }, + { + "task_id": "call_002", + "title": "Checked user permissions", + "status": "pending", + }, + { + "task_id": "call_003", + "title": "Generated comprehensive user report", + "status": "complete", + "output": { + "type": "rich_text", + "elements": [ + {"type": "rich_text_section", "elements": [{"type": "text", "text": "15 data points compiled"}]} + ], + }, + }, + ], + } + self.assertDictEqual(input, PlanBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + +# ---------------------------------------------- +# Task card +# ---------------------------------------------- + + +class TaskCardBlockTests(unittest.TestCase): + def test_document(self): + input = { + "type": "task_card", + "task_id": "task_1", + "title": "Fetching weather data", + "status": "pending", + "output": { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "Found weather data for Chicago from 2 sources"}], + } + ], + }, + "sources": [ + {"type": "url", "url": "https://weather.com/", "text": "weather.com"}, + {"type": "url", "url": "https://www.accuweather.com/", "text": "accuweather.com"}, + ], + } + self.assertDictEqual(input, TaskCardBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + # ---------------------------------------------- # Video # ---------------------------------------------- diff --git a/tests/slack_sdk/models/test_chunks.py b/tests/slack_sdk/models/test_chunks.py new file mode 100644 index 000000000..78845b307 --- /dev/null +++ b/tests/slack_sdk/models/test_chunks.py @@ -0,0 +1,91 @@ +import unittest + +from slack_sdk.models.blocks.block_elements import UrlSourceElement +from slack_sdk.models.messages.chunk import MarkdownTextChunk, PlanUpdateChunk, TaskUpdateChunk + + +class MarkdownTextChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + MarkdownTextChunk(text="greetings!").to_dict(), + { + "type": "markdown_text", + "text": "greetings!", + }, + ) + + +class PlanUpdateChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + PlanUpdateChunk(title="Crunching numbers...").to_dict(), + { + "type": "plan_update", + "title": "Crunching numbers...", + }, + ) + + +class TaskUpdateChunkTests(unittest.TestCase): + def test_json(self): + self.assertDictEqual( + TaskUpdateChunk(id="001", title="Waiting...", status="pending").to_dict(), + { + "type": "task_update", + "id": "001", + "title": "Waiting...", + "status": "pending", + }, + ) + self.assertDictEqual( + TaskUpdateChunk( + id="002", + title="Wondering...", + status="in_progress", + details="- Gathering information...", + ).to_dict(), + { + "type": "task_update", + "id": "002", + "title": "Wondering...", + "status": "in_progress", + "details": "- Gathering information...", + }, + ) + self.assertDictEqual( + TaskUpdateChunk( + id="003", + title="Answering...", + status="complete", + output="Found a solution", + sources=[ + UrlSourceElement( + text="Discussion of Life's Questions", + url="https://www.answers.com", + ), + UrlSourceElement( + text="The Free Encyclopedia", + url="https://wikipedia.org", + ), + ], + ).to_dict(), + { + "type": "task_update", + "id": "003", + "title": "Answering...", + "status": "complete", + "output": "Found a solution", + "sources": [ + { + "type": "url", + "text": "Discussion of Life's Questions", + "url": "https://www.answers.com", + }, + { + "type": "url", + "text": "The Free Encyclopedia", + "url": "https://wikipedia.org", + }, + ], + }, + ) diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py index 75c13c8c2..0a11b9d53 100644 --- a/tests/slack_sdk/web/test_chat_stream.py +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -7,6 +7,7 @@ from slack_sdk.models.blocks.basic_components import FeedbackButtonObject from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement from slack_sdk.models.blocks.blocks import ContextActionsBlock +from slack_sdk.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server from tests.slack_sdk.web.mock_web_api_handler import MockHandler @@ -105,7 +106,10 @@ def test_streams_a_short_message(self): stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) self.assertEqual(stop_request.get("channel"), "C0123456789") self.assertEqual(stop_request.get("ts"), "123.123") - self.assertEqual(stop_request.get("markdown_text"), "nice!") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "nice!", "type": "markdown_text"}]', + ) def test_streams_a_long_message(self): streamer = self.client.chat_stream( @@ -146,13 +150,19 @@ def test_streams_a_long_message(self): start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) self.assertEqual(start_request.get("channel"), "C0123456789") self.assertEqual(start_request.get("thread_ts"), "123.000") - self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual( + json.dumps(start_request.get("chunks")), + '[{"text": "**this messag", "type": "markdown_text"}]', + ) self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) self.assertEqual(append_request.get("channel"), "C0123456789") - self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "e is bold!", "type": "markdown_text"}]', + ) self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") self.assertEqual(append_request.get("ts"), "123.123") @@ -162,10 +172,76 @@ def test_streams_a_long_message(self): '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', ) self.assertEqual(stop_request.get("channel"), "C0123456789") - self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "**", "type": "markdown_text"}]', + ) self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") self.assertEqual(stop_request.get("ts"), "123.123") + def test_streams_a_chunk_message(self): + streamer = self.client.chat_stream( + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + task_display_mode="timeline", + ) + streamer.append(markdown_text="**this is ") + streamer.append(markdown_text="buffered**") + streamer.append( + chunks=[ + TaskUpdateChunk( + id="001", + title="Counting...", + status="pending", + ), + ], + ) + streamer.append( + chunks=[ + MarkdownTextChunk(text="**this is unbuffered**"), + ], + ) + streamer.append(markdown_text="\n") + streamer.stop( + chunks=[ + MarkdownTextChunk(text=":space_invader:"), + ], + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual( + json.dumps(start_request.get("chunks")), + '[{"text": "**this is buffered**", "type": "markdown_text"}, {"id": "001", "status": "pending", "title": "Counting...", "type": "task_update"}]', + ) + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + self.assertEqual(start_request.get("task_display_mode"), "timeline") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("ts"), "123.123") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "**this is unbuffered**", "type": "markdown_text"}]', + ) + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', + ) + def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = self.client.chat_stream( channel="C0123456789", diff --git a/tests/slack_sdk/web/test_internal_utils.py b/tests/slack_sdk/web/test_internal_utils.py index ac7704b30..3e44f0c9c 100644 --- a/tests/slack_sdk/web/test_internal_utils.py +++ b/tests/slack_sdk/web/test_internal_utils.py @@ -2,18 +2,17 @@ import unittest from io import BytesIO from pathlib import Path -from typing import Dict, Sequence, Union - -import pytest +from typing import Dict from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block, DividerBlock +from slack_sdk.models.messages.chunk import MarkdownTextChunk, PlanUpdateChunk, TaskUpdateChunk from slack_sdk.web.internal_utils import ( _build_unexpected_body_error_message, + _get_url, + _next_cursor_is_present, _parse_web_class_objects, _to_v2_file_upload_item, - _next_cursor_is_present, - _get_url, ) @@ -57,6 +56,25 @@ def test_can_parse_sequence_of_attachments(self): for attachment in kwargs["attachments"]: assert isinstance(attachment, Dict) + def test_can_parse_sequence_of_chunks(self): + for chunks in [ + [ + MarkdownTextChunk(text="fiz"), + PlanUpdateChunk(title="fuz"), + TaskUpdateChunk(id="001", title="baz", status="complete"), + ], # list + ( + MarkdownTextChunk(text="fiz"), + PlanUpdateChunk(title="fuz"), + TaskUpdateChunk(id="001", title="baz", status="complete"), + ), # tuple + ]: + kwargs = {"chunks": chunks} + _parse_web_class_objects(kwargs) + assert kwargs["chunks"] + for chunks in kwargs["chunks"]: + assert isinstance(chunks, Dict) + def test_can_parse_str_blocks(self): input = json.dumps([Block(block_id="42").to_dict(), Block(block_id="24").to_dict()]) kwargs = {"blocks": input} @@ -71,6 +89,19 @@ def test_can_parse_str_attachments(self): assert isinstance(kwargs["attachments"], str) assert input == kwargs["attachments"] + def test_can_parse_str_chunks(self): + input = json.dumps( + [ + MarkdownTextChunk(text="fiz").to_dict(), + PlanUpdateChunk(title="fuz").to_dict(), + TaskUpdateChunk(id="001", title="baz", status="complete").to_dict(), + ] + ) + kwargs = {"chunks": input} + _parse_web_class_objects(kwargs) + assert isinstance(kwargs["chunks"], str) + assert input == kwargs["chunks"] + def test_can_parse_user_auth_blocks(self): kwargs = { "channel": "C12345", diff --git a/tests/slack_sdk_async/web/test_async_chat_stream.py b/tests/slack_sdk_async/web/test_async_chat_stream.py index 212fee1e2..2a4f5b931 100644 --- a/tests/slack_sdk_async/web/test_async_chat_stream.py +++ b/tests/slack_sdk_async/web/test_async_chat_stream.py @@ -6,6 +6,7 @@ from slack_sdk.models.blocks.basic_components import FeedbackButtonObject from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement from slack_sdk.models.blocks.blocks import ContextActionsBlock +from slack_sdk.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk from slack_sdk.web.async_client import AsyncWebClient from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server from tests.slack_sdk.web.mock_web_api_handler import MockHandler @@ -107,7 +108,10 @@ async def test_streams_a_short_message(self): stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) self.assertEqual(stop_request.get("channel"), "C0123456789") self.assertEqual(stop_request.get("ts"), "123.123") - self.assertEqual(stop_request.get("markdown_text"), "nice!") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "nice!", "type": "markdown_text"}]', + ) @async_test async def test_streams_a_long_message(self): @@ -149,13 +153,19 @@ async def test_streams_a_long_message(self): start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) self.assertEqual(start_request.get("channel"), "C0123456789") self.assertEqual(start_request.get("thread_ts"), "123.000") - self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual( + json.dumps(start_request.get("chunks")), + '[{"text": "**this messag", "type": "markdown_text"}]', + ) self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) self.assertEqual(append_request.get("channel"), "C0123456789") - self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "e is bold!", "type": "markdown_text"}]', + ) self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") self.assertEqual(append_request.get("ts"), "123.123") @@ -165,10 +175,75 @@ async def test_streams_a_long_message(self): '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', ) self.assertEqual(stop_request.get("channel"), "C0123456789") - self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "**", "type": "markdown_text"}]', + ) self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") self.assertEqual(stop_request.get("ts"), "123.123") + @async_test + async def test_streams_a_chunk_message(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="**this is ") + await streamer.append(markdown_text="buffered**") + await streamer.append( + chunks=[ + TaskUpdateChunk( + id="001", + title="Counting...", + status="pending", + ), + ], + ) + await streamer.append( + chunks=[ + MarkdownTextChunk(text="**this is unbuffered**"), + ], + ) + await streamer.append(markdown_text="\n") + await streamer.stop( + chunks=[ + MarkdownTextChunk(text=":space_invader:"), + ], + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual( + json.dumps(start_request.get("chunks")), + '[{"text": "**this is buffered**", "type": "markdown_text"}, {"id": "001", "status": "pending", "title": "Counting...", "type": "task_update"}]', + ) + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("ts"), "123.123") + self.assertEqual( + json.dumps(append_request.get("chunks")), + '[{"text": "**this is unbuffered**", "type": "markdown_text"}]', + ) + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual( + json.dumps(stop_request.get("chunks")), + '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', + ) + @async_test async def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = await self.client.chat_stream( From 8b0bd0b50128acb2da1b766d0a74d1897d173f7b Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 10 Feb 2026 14:04:43 -0800 Subject: [PATCH 28/30] chore(release): version 3.40.0 (#1831) --- .../models/blocks/basic_components.html | 12 ++++----- .../models/blocks/block_elements.html | 8 +++--- docs/reference/models/blocks/blocks.html | 8 +++--- docs/reference/models/blocks/index.html | 26 +++++++++---------- docs/reference/models/messages/chunk.html | 2 +- docs/reference/models/views/index.html | 4 +-- slack_sdk/version.py | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/reference/models/blocks/basic_components.html b/docs/reference/models/blocks/basic_components.html index 50554265d..2821b6a4b 100644 --- a/docs/reference/models/blocks/basic_components.html +++ b/docs/reference/models/blocks/basic_components.html @@ -188,7 +188,7 @@

    Class variables

    Static methods

    -def parse(confirm: ConfirmObject | Dict[str, Any]) +def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any])
    @@ -324,7 +324,7 @@

    Class variables

    Static methods

    -def parse(config: DispatchActionConfig | Dict[str, Any]) +def parse(config: ForwardRef('DispatchActionConfig') | Dict[str, Any])
    @@ -446,7 +446,7 @@

    Class variables

    Static methods

    -def parse(feedback_button: FeedbackButtonObject | Dict[str, Any]) +def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any])
    @@ -907,7 +907,7 @@

    Static methods

    Creates a simple Option instance with the same value and label

    -def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None
    @@ -1105,7 +1105,7 @@

    Class variables

    Static methods

    -def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None
    @@ -1536,7 +1536,7 @@

    Class variables

    Static methods

    -def parse(text: str | Dict[str, Any] | TextObject,
    default_type: str = 'mrkdwn') ‑> TextObject | None
    +def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
    default_type: str = 'mrkdwn') ‑> TextObject | None
    diff --git a/docs/reference/models/blocks/block_elements.html b/docs/reference/models/blocks/block_elements.html index e79ac10d1..880573706 100644 --- a/docs/reference/models/blocks/block_elements.html +++ b/docs/reference/models/blocks/block_elements.html @@ -146,13 +146,13 @@

    Class variables

    Static methods

    -def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None +def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None
    -def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject]
    @@ -829,7 +829,7 @@

    Class variables

    Static methods

    -def parse(filter: dict | ConversationFilter) +def parse(filter: dict | ForwardRef('ConversationFilter'))
    @@ -3505,7 +3505,7 @@

    Class variables

    class RichTextInputElement -(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | RichTextBlock | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    +(*,
    action_id: str | None = None,
    placeholder: str | dict | TextObject | None = None,
    initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
    dispatch_action_config: dict | DispatchActionConfig | None = None,
    focus_on_load: bool | None = None,
    **others: dict)
    diff --git a/docs/reference/models/blocks/blocks.html b/docs/reference/models/blocks/blocks.html index a4f511858..af825e54a 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -298,13 +298,13 @@

    Class variables

    Static methods

    -def parse(block: dict | Block) ‑> Block | None +def parse(block: dict | ForwardRef('Block')) ‑> Block | None
    -def parse_all(blocks: Sequence[dict | Block] | None) ‑> List[Block] +def parse_all(blocks: Sequence[dict | ForwardRef('Block')] | None) ‑> List[Block]
    @@ -1365,7 +1365,7 @@

    Inherited members

    block_id should be unique for each message and each iteration of a message. If a message is updated, use a new block_id. title (required): Title of the plan in plain text - tasks: Details of the task in the form of a single "rich_text" entity. + tasks: A sequence of task card blocks. Each task represents a single action within the plan. """ super().__init__(type=self.type, block_id=block_id) show_unknown_key_warning(self, others) @@ -1388,7 +1388,7 @@

    Args

    title : required
    Title of the plan in plain text
    tasks
    -
    Details of the task in the form of a single "rich_text" entity.
    +
    A sequence of task card blocks. Each task represents a single action within the plan.

    Ancestors

      diff --git a/docs/reference/models/blocks/index.html b/docs/reference/models/blocks/index.html index 4444d208b..536590edf 100644 --- a/docs/reference/models/blocks/index.html +++ b/docs/reference/models/blocks/index.html @@ -320,13 +320,13 @@

      Class variables

      Static methods

      -def parse(block: dict | Block) ‑> Block | None +def parse(block: dict | ForwardRef('Block')) ‑> Block | None
      -def parse_all(blocks: Sequence[dict | Block] | None) ‑> List[Block] +def parse_all(blocks: Sequence[dict | ForwardRef('Block')] | None) ‑> List[Block]
      @@ -458,13 +458,13 @@

      Class variables

      Static methods

      -def parse(block_element: dict | BlockElement) ‑> BlockElement | TextObject | None +def parse(block_element: dict | ForwardRef('BlockElement')) ‑> BlockElement | TextObject | None
      -def parse_all(block_elements: Sequence[dict | BlockElement | TextObject]) ‑> List[BlockElement | TextObject] +def parse_all(block_elements: Sequence[dict | ForwardRef('BlockElement') | TextObject]) ‑> List[BlockElement | TextObject]
      @@ -1278,7 +1278,7 @@

      Class variables

      Static methods

      -def parse(confirm: ConfirmObject | Dict[str, Any]) +def parse(confirm: ForwardRef('ConfirmObject') | Dict[str, Any])
      @@ -1650,7 +1650,7 @@

      Class variables

      Static methods

      -def parse(filter: dict | ConversationFilter) +def parse(filter: dict | ForwardRef('ConversationFilter'))
      @@ -2842,7 +2842,7 @@

      Class variables

      Static methods

      -def parse(feedback_button: FeedbackButtonObject | Dict[str, Any]) +def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any])
      @@ -4727,7 +4727,7 @@

      Static methods

      Creates a simple Option instance with the same value and label

      -def parse_all(options: Sequence[Dict[str, Any] | Option] | None) ‑> List[Option] | None +def parse_all(options: Sequence[Dict[str, Any] | ForwardRef('Option')] | None) ‑> List[Option] | None
      @@ -4925,7 +4925,7 @@

      Class variables

      Static methods

      -def parse_all(option_groups: Sequence[Dict[str, Any] | OptionGroup] | None) ‑> List[OptionGroup] | None +def parse_all(option_groups: Sequence[Dict[str, Any] | ForwardRef('OptionGroup')] | None) ‑> List[OptionGroup] | None
      @@ -5390,7 +5390,7 @@

      Inherited members

      block_id should be unique for each message and each iteration of a message. If a message is updated, use a new block_id. title (required): Title of the plan in plain text - tasks: Details of the task in the form of a single "rich_text" entity. + tasks: A sequence of task card blocks. Each task represents a single action within the plan. """ super().__init__(type=self.type, block_id=block_id) show_unknown_key_warning(self, others) @@ -5413,7 +5413,7 @@

      Args

      title : required
      Title of the plan in plain text
      tasks
      -
      Details of the task in the form of a single "rich_text" entity.
      +
      A sequence of task card blocks. Each task represents a single action within the plan.

      Ancestors

        @@ -6143,7 +6143,7 @@

        Class variables

      class RichTextInputElement -(*,
      action_id: str | None = None,
      placeholder: str | dict | TextObject | None = None,
      initial_value: Dict[str, Any] | RichTextBlock | None = None,
      dispatch_action_config: dict | DispatchActionConfig | None = None,
      focus_on_load: bool | None = None,
      **others: dict)
      +(*,
      action_id: str | None = None,
      placeholder: str | dict | TextObject | None = None,
      initial_value: Dict[str, Any] | ForwardRef('RichTextBlock') | None = None,
      dispatch_action_config: dict | DispatchActionConfig | None = None,
      focus_on_load: bool | None = None,
      **others: dict)
      @@ -7547,7 +7547,7 @@

      Class variables

      Static methods

      -def parse(text: str | Dict[str, Any] | TextObject,
      default_type: str = 'mrkdwn') ‑> TextObject | None
      +def parse(text: str | Dict[str, Any] | ForwardRef('TextObject'),
      default_type: str = 'mrkdwn') ‑> TextObject | None
      diff --git a/docs/reference/models/messages/chunk.html b/docs/reference/models/messages/chunk.html index 49c1ef1bc..100f83fa4 100644 --- a/docs/reference/models/messages/chunk.html +++ b/docs/reference/models/messages/chunk.html @@ -121,7 +121,7 @@

      Class variables

      Static methods

      -def parse(chunk: Dict | Chunk) ‑> Chunk | None +def parse(chunk: Dict | ForwardRef('Chunk')) ‑> Chunk | None
      diff --git a/docs/reference/models/views/index.html b/docs/reference/models/views/index.html index b7bad66bc..d9e60359b 100644 --- a/docs/reference/models/views/index.html +++ b/docs/reference/models/views/index.html @@ -48,7 +48,7 @@

      Classes

      class View -(type: str,
      id: str | None = None,
      callback_id: str | None = None,
      external_id: str | None = None,
      team_id: str | None = None,
      bot_id: str | None = None,
      app_id: str | None = None,
      root_view_id: str | None = None,
      previous_view_id: str | None = None,
      title: str | dict | PlainTextObject | None = None,
      submit: str | dict | PlainTextObject | None = None,
      close: str | dict | PlainTextObject | None = None,
      blocks: Sequence[dict | Block] | None = None,
      private_metadata: str | None = None,
      state: dict | ViewState | None = None,
      hash: str | None = None,
      clear_on_close: bool | None = None,
      notify_on_close: bool | None = None,
      **kwargs)
      +(type: str,
      id: str | None = None,
      callback_id: str | None = None,
      external_id: str | None = None,
      team_id: str | None = None,
      bot_id: str | None = None,
      app_id: str | None = None,
      root_view_id: str | None = None,
      previous_view_id: str | None = None,
      title: str | dict | PlainTextObject | None = None,
      submit: str | dict | PlainTextObject | None = None,
      close: str | dict | PlainTextObject | None = None,
      blocks: Sequence[dict | Block] | None = None,
      private_metadata: str | None = None,
      state: dict | ForwardRef('ViewState') | None = None,
      hash: str | None = None,
      clear_on_close: bool | None = None,
      notify_on_close: bool | None = None,
      **kwargs)
      @@ -229,7 +229,7 @@

      Inherited members

      class ViewState -(*,
      values: Dict[str, Dict[str, dict | ViewStateValue]])
      +(*,
      values: Dict[str, Dict[str, dict | ForwardRef('ViewStateValue')]])
      diff --git a/slack_sdk/version.py b/slack_sdk/version.py index fb572e0ba..a2910c423 100644 --- a/slack_sdk/version.py +++ b/slack_sdk/version.py @@ -1,3 +1,3 @@ """Check the latest version at https://pypi.org/project/slack-sdk/""" -__version__ = "3.39.0" +__version__ = "3.40.0" From 506582bb4a79c7487370cadfd5a322b0196adf29 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 18 Feb 2026 12:53:31 -0800 Subject: [PATCH 29/30] fix: rely on naive datetime for sqlalchemy (#1833) --- .github/workflows/ci-build.yml | 50 +++++++++++++++++++ requirements/databases.txt | 5 ++ .../installation_store/sqlalchemy/__init__.py | 11 ++++ slack_sdk/oauth/sqlalchemy_utils/__init__.py | 33 ++++++++++++ .../oauth/state_store/sqlalchemy/__init__.py | 11 ++-- .../test_async_sqlalchemy.py | 34 ++++++++++++- .../installation_store/test_sqlalchemy.py | 33 +++++++++++- .../state_store/test_async_sqlalchemy.py | 24 ++++++++- .../oauth/state_store/test_sqlalchemy.py | 23 ++++++++- 9 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 requirements/databases.txt create mode 100644 slack_sdk/oauth/sqlalchemy_utils/__init__.py diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index c705fbb7d..2cdb5b9d5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -114,6 +114,55 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true + databases: + # TODO: Add MySQL and other database testing when possible + name: Database Unit Tests + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + services: + postgres: + image: postgres@sha256:e4842c8a99ca99339e1693e6fe5fe62c7becb31991f066f989047dfb2fbf47af # 16 + env: + POSTGRES_USER: test_user + POSTGRES_PASSWORD: password + POSTGRES_DB: test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + cache: pip + - name: Install dependencies + run: | + pip install -U pip + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt + pip install -r requirements/databases.txt + - name: Run sync tests (PostgreSQL) + env: + TEST_DATABASE_URL: postgresql://test_user:password@localhost/test + run: | + PYTHONPATH=$PWD:$PYTHONPATH pytest -s tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py + PYTHONPATH=$PWD:$PYTHONPATH pytest -s tests/slack_sdk/oauth/state_store/test_sqlalchemy.py + - name: Run async tests (PostgreSQL) + env: + ASYNC_TEST_DATABASE_URL: postgresql+asyncpg://test_user:password@localhost/test + run: | + PYTHONPATH=$PWD:$PYTHONPATH pytest -s tests/slack_sdk/oauth/installation_store/test_async_sqlalchemy.py + PYTHONPATH=$PWD:$PYTHONPATH pytest -s tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py + notifications: name: Regression notifications runs-on: ubuntu-latest @@ -121,6 +170,7 @@ jobs: - lint - typecheck - unittest + - databases if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} steps: - name: Send notifications of failing tests diff --git a/requirements/databases.txt b/requirements/databases.txt new file mode 100644 index 000000000..a7cd29b5f --- /dev/null +++ b/requirements/databases.txt @@ -0,0 +1,5 @@ +# Database drivers for CI testing + +# PostgreSQL drivers +psycopg2-binary>=2.9,<3 +asyncpg>=0.27,<1 diff --git a/slack_sdk/oauth/installation_store/sqlalchemy/__init__.py b/slack_sdk/oauth/installation_store/sqlalchemy/__init__.py index f629deead..942602b36 100644 --- a/slack_sdk/oauth/installation_store/sqlalchemy/__init__.py +++ b/slack_sdk/oauth/installation_store/sqlalchemy/__init__.py @@ -23,6 +23,7 @@ from slack_sdk.oauth.installation_store.async_installation_store import ( AsyncInstallationStore, ) +from slack_sdk.oauth.sqlalchemy_utils import normalize_datetime_for_db class SQLAlchemyInstallationStore(InstallationStore): @@ -140,6 +141,9 @@ def save(self, installation: Installation): with self.engine.begin() as conn: i = installation.to_dict() i["client_id"] = self.client_id + i["installed_at"] = normalize_datetime_for_db(i.get("installed_at")) + i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at")) + i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at")) i_column = self.installations.c installations_rows = conn.execute( @@ -171,6 +175,8 @@ def save_bot(self, bot: Bot): # bots b = bot.to_dict() b["client_id"] = self.client_id + b["installed_at"] = normalize_datetime_for_db(b.get("installed_at")) + b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at")) b_column = self.bots.c bots_rows = conn.execute( @@ -419,6 +425,9 @@ async def async_save(self, installation: Installation): async with self.engine.begin() as conn: i = installation.to_dict() i["client_id"] = self.client_id + i["installed_at"] = normalize_datetime_for_db(i.get("installed_at")) + i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at")) + i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at")) i_column = self.installations.c installations_rows = await conn.execute( @@ -450,6 +459,8 @@ async def async_save_bot(self, bot: Bot): # bots b = bot.to_dict() b["client_id"] = self.client_id + b["installed_at"] = normalize_datetime_for_db(b.get("installed_at")) + b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at")) b_column = self.bots.c bots_rows = await conn.execute( diff --git a/slack_sdk/oauth/sqlalchemy_utils/__init__.py b/slack_sdk/oauth/sqlalchemy_utils/__init__.py new file mode 100644 index 000000000..a0692bda3 --- /dev/null +++ b/slack_sdk/oauth/sqlalchemy_utils/__init__.py @@ -0,0 +1,33 @@ +from datetime import datetime +from typing import Optional + + +# TODO: Remove this function in next major release (v4.0.0) after updating all +# DateTime columns to DateTime(timezone=True). See issue #1832 for context. +def normalize_datetime_for_db(dt: Optional[datetime]) -> Optional[datetime]: + """ + Normalize timezone-aware datetime to naive UTC datetime for database storage. + + Ensures compatibility with existing databases using TIMESTAMP WITHOUT TIME ZONE. + SQLAlchemy DateTime columns without timezone=True create naive timestamp columns + in databases like PostgreSQL. This function strips timezone information from + timezone-aware datetimes (which are already in UTC) to enable safe comparisons. + + Args: + dt: A timezone-aware or naive datetime object, or None + + Returns: + A naive datetime in UTC, or None if input is None + + Example: + >>> from datetime import datetime, timezone + >>> aware_dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + >>> naive_dt = normalize_datetime_for_db(aware_dt) + >>> naive_dt.tzinfo is None + True + """ + if dt is None: + return None + if dt.tzinfo is not None: + return dt.replace(tzinfo=None) + return dt diff --git a/slack_sdk/oauth/state_store/sqlalchemy/__init__.py b/slack_sdk/oauth/state_store/sqlalchemy/__init__.py index 8bb3ec1ff..3898c5b32 100644 --- a/slack_sdk/oauth/state_store/sqlalchemy/__init__.py +++ b/slack_sdk/oauth/state_store/sqlalchemy/__init__.py @@ -10,6 +10,7 @@ from sqlalchemy import Table, Column, Integer, String, DateTime, and_, MetaData from sqlalchemy.engine import Engine from sqlalchemy.ext.asyncio import AsyncEngine +from slack_sdk.oauth.sqlalchemy_utils import normalize_datetime_for_db class SQLAlchemyOAuthStateStore(OAuthStateStore): @@ -55,7 +56,7 @@ def logger(self) -> Logger: def issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) + now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)) with self.engine.begin() as conn: conn.execute( self.oauth_states.insert(), @@ -65,9 +66,10 @@ def issue(self, *args, **kwargs) -> str: def consume(self, state: str) -> bool: try: + now = normalize_datetime_for_db(datetime.now(tz=timezone.utc)) with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now)) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -124,7 +126,7 @@ def logger(self) -> Logger: async def async_issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) + now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)) async with self.engine.begin() as conn: await conn.execute( self.oauth_states.insert(), @@ -134,9 +136,10 @@ async def async_issue(self, *args, **kwargs) -> str: async def async_consume(self, state: str) -> bool: try: + now = normalize_datetime_for_db(datetime.now(tz=timezone.utc)) async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now)) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") diff --git a/tests/slack_sdk/oauth/installation_store/test_async_sqlalchemy.py b/tests/slack_sdk/oauth/installation_store/test_async_sqlalchemy.py index 35aa79623..6370ffb77 100644 --- a/tests/slack_sdk/oauth/installation_store/test_async_sqlalchemy.py +++ b/tests/slack_sdk/oauth/installation_store/test_async_sqlalchemy.py @@ -1,3 +1,4 @@ +import os import unittest from tests.helpers import async_test from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine @@ -5,13 +6,20 @@ from slack_sdk.oauth.installation_store import Installation from slack_sdk.oauth.installation_store.sqlalchemy import AsyncSQLAlchemyInstallationStore +database_url = os.environ.get("ASYNC_TEST_DATABASE_URL", "sqlite+aiosqlite:///:memory:") + + +def setUpModule(): + """Emit database configuration for CI visibility across builds.""" + print(f"\n[InstallationStore/AsyncSQLAlchemy] Database: {database_url}") + class TestAsyncSQLAlchemy(unittest.TestCase): engine: AsyncEngine @async_test async def setUp(self): - self.engine = create_async_engine("sqlite+aiosqlite:///:memory:") + self.engine = create_async_engine(database_url) self.store = AsyncSQLAlchemyInstallationStore(client_id="111.222", engine=self.engine) async with self.engine.begin() as conn: await conn.run_sync(self.store.metadata.create_all) @@ -296,3 +304,27 @@ async def test_issue_1441_mixing_user_and_bot_installations(self): self.assertIsNone(installation) installation = await store.async_find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(installation) + + @async_test + async def test_timezone_aware_datetime_compatibility(self): + installation = Installation( + app_id="A111", + enterprise_id="E111", + team_id="T111", + user_id="U111", + bot_id="B111", + bot_token="xoxb-111", + bot_scopes=["chat:write"], + bot_user_id="U222", + ) + + # First save + await self.store.async_save(installation) + found = await self.store.async_find_installation(enterprise_id="E111", team_id="T111") + self.assertIsNotNone(found) + self.assertEqual(found.app_id, "A111") + + # Second save (update) - tests WHERE clause with installed_at comparison + await self.store.async_save(installation) + found = await self.store.async_find_installation(enterprise_id="E111", team_id="T111") + self.assertIsNotNone(found) diff --git a/tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py b/tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py index 4d827f70b..75568e94c 100644 --- a/tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py +++ b/tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py @@ -1,3 +1,4 @@ +import os import unittest import sqlalchemy @@ -6,12 +7,19 @@ from slack_sdk.oauth.installation_store import Installation from slack_sdk.oauth.installation_store.sqlalchemy import SQLAlchemyInstallationStore +database_url = os.environ.get("TEST_DATABASE_URL", "sqlite:///:memory:") + + +def setUpModule(): + """Emit database configuration for CI visibility across builds.""" + print(f"\n[InstallationStore/SQLAlchemy] Database: {database_url}") + class TestSQLAlchemy(unittest.TestCase): engine: Engine def setUp(self): - self.engine = sqlalchemy.create_engine("sqlite:///:memory:") + self.engine = sqlalchemy.create_engine(database_url) self.store = SQLAlchemyInstallationStore(client_id="111.222", engine=self.engine) self.store.metadata.create_all(self.engine) @@ -289,3 +297,26 @@ def test_issue_1441_mixing_user_and_bot_installations(self): self.assertIsNone(installation) installation = store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(installation) + + def test_timezone_aware_datetime_compatibility(self): + installation = Installation( + app_id="A111", + enterprise_id="E111", + team_id="T111", + user_id="U111", + bot_id="B111", + bot_token="xoxb-111", + bot_scopes=["chat:write"], + bot_user_id="U222", + ) + + # First save + self.store.save(installation) + found = self.store.find_installation(enterprise_id="E111", team_id="T111") + self.assertIsNotNone(found) + self.assertEqual(found.app_id, "A111") + + # Second save (update) - tests WHERE clause with installed_at comparison + self.store.save(installation) + found = self.store.find_installation(enterprise_id="E111", team_id="T111") + self.assertIsNotNone(found) diff --git a/tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py b/tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py index 87886c6ee..74bfcfe6e 100644 --- a/tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py +++ b/tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py @@ -1,17 +1,25 @@ import asyncio +import os import unittest from tests.helpers import async_test from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine from slack_sdk.oauth.state_store.sqlalchemy import AsyncSQLAlchemyOAuthStateStore +database_url = os.environ.get("ASYNC_TEST_DATABASE_URL", "sqlite+aiosqlite:///:memory:") + + +def setUpModule(): + """Emit database configuration for CI visibility across builds.""" + print(f"\n[StateStore/AsyncSQLAlchemy] Database: {database_url}") + class TestSQLAlchemy(unittest.TestCase): engine: AsyncEngine @async_test async def setUp(self): - self.engine = create_async_engine("sqlite+aiosqlite:///:memory:") + self.engine = create_async_engine(database_url) self.store = AsyncSQLAlchemyOAuthStateStore(engine=self.engine, expiration_seconds=2) async with self.engine.begin() as conn: await conn.run_sync(self.store.metadata.create_all) @@ -36,3 +44,17 @@ async def test_expiration(self): await asyncio.sleep(3) result = await self.store.async_consume(state) self.assertFalse(result) + + @async_test + async def test_timezone_aware_datetime_compatibility(self): + # Issue a state (tests INSERT with timezone-aware datetime) + state = await self.store.async_issue() + self.assertIsNotNone(state) + + # Consume it immediately (tests WHERE clause comparison with timezone-aware datetime) + result = await self.store.async_consume(state) + self.assertTrue(result) + + # Second consume should fail (state already consumed) + result = await self.store.async_consume(state) + self.assertFalse(result) diff --git a/tests/slack_sdk/oauth/state_store/test_sqlalchemy.py b/tests/slack_sdk/oauth/state_store/test_sqlalchemy.py index 441400d60..1a2940a81 100644 --- a/tests/slack_sdk/oauth/state_store/test_sqlalchemy.py +++ b/tests/slack_sdk/oauth/state_store/test_sqlalchemy.py @@ -1,3 +1,4 @@ +import os import time import unittest @@ -6,12 +7,19 @@ from slack_sdk.oauth.state_store.sqlalchemy import SQLAlchemyOAuthStateStore +database_url = os.environ.get("TEST_DATABASE_URL", "sqlite:///:memory:") + + +def setUpModule(): + """Emit database configuration for CI visibility across builds.""" + print(f"\n[StateStore/SQLAlchemy] Database: {database_url}") + class TestSQLAlchemy(unittest.TestCase): engine: Engine def setUp(self): - self.engine = sqlalchemy.create_engine("sqlite:///:memory:") + self.engine = sqlalchemy.create_engine(database_url) self.store = SQLAlchemyOAuthStateStore(engine=self.engine, expiration_seconds=2) self.store.metadata.create_all(self.engine) @@ -31,3 +39,16 @@ def test_expiration(self): time.sleep(3) result = self.store.consume(state) self.assertFalse(result) + + def test_timezone_aware_datetime_compatibility(self): + # Issue a state (tests INSERT with timezone-aware datetime) + state = self.store.issue() + self.assertIsNotNone(state) + + # Consume it immediately (tests WHERE clause comparison with timezone-aware datetime) + result = self.store.consume(state) + self.assertTrue(result) + + # Second consume should fail (state already consumed) + result = self.store.consume(state) + self.assertFalse(result) From f14e689e11c2509891b23cb51044451725ef407f Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 18 Feb 2026 13:35:31 -0800 Subject: [PATCH 30/30] chore(release): version 3.40.1 (#1834) --- docs/reference/oauth/index.html | 5 + .../installation_store/sqlalchemy/index.html | 10 ++ .../oauth/sqlalchemy_utils/index.html | 130 ++++++++++++++++++ .../oauth/state_store/sqlalchemy/index.html | 20 +-- slack_sdk/version.py | 2 +- 5 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 docs/reference/oauth/sqlalchemy_utils/index.html diff --git a/docs/reference/oauth/index.html b/docs/reference/oauth/index.html index 8fe69e5d0..a7a9199e2 100644 --- a/docs/reference/oauth/index.html +++ b/docs/reference/oauth/index.html @@ -54,6 +54,10 @@

      Sub-modules

      +
      slack_sdk.oauth.sqlalchemy_utils
      +
      +
      +
      slack_sdk.oauth.state_store

      OAuth state parameter data store …

      @@ -865,6 +869,7 @@

      Methods

    • slack_sdk.oauth.authorize_url_generator
    • slack_sdk.oauth.installation_store
    • slack_sdk.oauth.redirect_uri_page_renderer
    • +
    • slack_sdk.oauth.sqlalchemy_utils
    • slack_sdk.oauth.state_store
    • slack_sdk.oauth.state_utils
    • slack_sdk.oauth.token_rotation
    • diff --git a/docs/reference/oauth/installation_store/sqlalchemy/index.html b/docs/reference/oauth/installation_store/sqlalchemy/index.html index 8861dd476..d19743757 100644 --- a/docs/reference/oauth/installation_store/sqlalchemy/index.html +++ b/docs/reference/oauth/installation_store/sqlalchemy/index.html @@ -99,6 +99,9 @@

      Classes

      async with self.engine.begin() as conn: i = installation.to_dict() i["client_id"] = self.client_id + i["installed_at"] = normalize_datetime_for_db(i.get("installed_at")) + i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at")) + i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at")) i_column = self.installations.c installations_rows = await conn.execute( @@ -130,6 +133,8 @@

      Classes

      # bots b = bot.to_dict() b["client_id"] = self.client_id + b["installed_at"] = normalize_datetime_for_db(b.get("installed_at")) + b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at")) b_column = self.bots.c bots_rows = await conn.execute( @@ -526,6 +531,9 @@

      Inherited members

      with self.engine.begin() as conn: i = installation.to_dict() i["client_id"] = self.client_id + i["installed_at"] = normalize_datetime_for_db(i.get("installed_at")) + i["bot_token_expires_at"] = normalize_datetime_for_db(i.get("bot_token_expires_at")) + i["user_token_expires_at"] = normalize_datetime_for_db(i.get("user_token_expires_at")) i_column = self.installations.c installations_rows = conn.execute( @@ -557,6 +565,8 @@

      Inherited members

      # bots b = bot.to_dict() b["client_id"] = self.client_id + b["installed_at"] = normalize_datetime_for_db(b.get("installed_at")) + b["bot_token_expires_at"] = normalize_datetime_for_db(b.get("bot_token_expires_at")) b_column = self.bots.c bots_rows = conn.execute( diff --git a/docs/reference/oauth/sqlalchemy_utils/index.html b/docs/reference/oauth/sqlalchemy_utils/index.html new file mode 100644 index 000000000..d82260a1d --- /dev/null +++ b/docs/reference/oauth/sqlalchemy_utils/index.html @@ -0,0 +1,130 @@ + + + + + + +slack_sdk.oauth.sqlalchemy_utils API documentation + + + + + + + + + + + +
      +
      +
      +

      Module slack_sdk.oauth.sqlalchemy_utils

      +
      +
      +
      +
      +
      +
      +
      +
      +

      Functions

      +
      +
      +def normalize_datetime_for_db(dt: datetime.datetime | None) ‑> datetime.datetime | None +
      +
      +
      + +Expand source code + +
      def normalize_datetime_for_db(dt: Optional[datetime]) -> Optional[datetime]:
      +    """
      +    Normalize timezone-aware datetime to naive UTC datetime for database storage.
      +
      +    Ensures compatibility with existing databases using TIMESTAMP WITHOUT TIME ZONE.
      +    SQLAlchemy DateTime columns without timezone=True create naive timestamp columns
      +    in databases like PostgreSQL. This function strips timezone information from
      +    timezone-aware datetimes (which are already in UTC) to enable safe comparisons.
      +
      +    Args:
      +        dt: A timezone-aware or naive datetime object, or None
      +
      +    Returns:
      +        A naive datetime in UTC, or None if input is None
      +
      +    Example:
      +        >>> from datetime import datetime, timezone
      +        >>> aware_dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
      +        >>> naive_dt = normalize_datetime_for_db(aware_dt)
      +        >>> naive_dt.tzinfo is None
      +        True
      +    """
      +    if dt is None:
      +        return None
      +    if dt.tzinfo is not None:
      +        return dt.replace(tzinfo=None)
      +    return dt
      +
      +

      Normalize timezone-aware datetime to naive UTC datetime for database storage.

      +

      Ensures compatibility with existing databases using TIMESTAMP WITHOUT TIME ZONE. +SQLAlchemy DateTime columns without timezone=True create naive timestamp columns +in databases like PostgreSQL. This function strips timezone information from +timezone-aware datetimes (which are already in UTC) to enable safe comparisons.

      +

      Args

      +
      +
      dt
      +
      A timezone-aware or naive datetime object, or None
      +
      +

      Returns

      +

      A naive datetime in UTC, or None if input is None

      +

      Example

      +
      >>> from datetime import datetime, timezone
      +>>> aware_dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
      +>>> naive_dt = normalize_datetime_for_db(aware_dt)
      +>>> naive_dt.tzinfo is None
      +True
      +
      +
      +
      +
      +
      +
      +
      + +
      + + + diff --git a/docs/reference/oauth/state_store/sqlalchemy/index.html b/docs/reference/oauth/state_store/sqlalchemy/index.html index a35fa3171..341fc10be 100644 --- a/docs/reference/oauth/state_store/sqlalchemy/index.html +++ b/docs/reference/oauth/state_store/sqlalchemy/index.html @@ -99,7 +99,7 @@

      Classes

      async def async_issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) + now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)) async with self.engine.begin() as conn: await conn.execute( self.oauth_states.insert(), @@ -109,9 +109,10 @@

      Classes

      async def async_consume(self, state: str) -> bool: try: + now = normalize_datetime_for_db(datetime.now(tz=timezone.utc)) async with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now)) result = await conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -189,9 +190,10 @@

      Methods

      async def async_consume(self, state: str) -> bool:
           try:
      +        now = normalize_datetime_for_db(datetime.now(tz=timezone.utc))
               async with self.engine.begin() as conn:
                   c = self.oauth_states.c
      -            query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc)))
      +            query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now))
                   result = await conn.execute(query)
                   for row in result.mappings():
                       self.logger.debug(f"consume's query result: {row}")
      @@ -215,7 +217,7 @@ 

      Methods

      async def async_issue(self, *args, **kwargs) -> str:
           state: str = str(uuid4())
      -    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
      +    now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc))
           async with self.engine.begin() as conn:
               await conn.execute(
                   self.oauth_states.insert(),
      @@ -293,7 +295,7 @@ 

      Methods

      def issue(self, *args, **kwargs) -> str: state: str = str(uuid4()) - now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc) + now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)) with self.engine.begin() as conn: conn.execute( self.oauth_states.insert(), @@ -303,9 +305,10 @@

      Methods

      def consume(self, state: str) -> bool: try: + now = normalize_datetime_for_db(datetime.now(tz=timezone.utc)) with self.engine.begin() as conn: c = self.oauth_states.c - query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc))) + query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now)) result = conn.execute(query) for row in result.mappings(): self.logger.debug(f"consume's query result: {row}") @@ -383,9 +386,10 @@

      Methods

      def consume(self, state: str) -> bool:
           try:
      +        now = normalize_datetime_for_db(datetime.now(tz=timezone.utc))
               with self.engine.begin() as conn:
                   c = self.oauth_states.c
      -            query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc)))
      +            query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > now))
                   result = conn.execute(query)
                   for row in result.mappings():
                       self.logger.debug(f"consume's query result: {row}")
      @@ -422,7 +426,7 @@ 

      Methods

      def issue(self, *args, **kwargs) -> str:
           state: str = str(uuid4())
      -    now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
      +    now = normalize_datetime_for_db(datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc))
           with self.engine.begin() as conn:
               conn.execute(
                   self.oauth_states.insert(),
      diff --git a/slack_sdk/version.py b/slack_sdk/version.py
      index a2910c423..f5a37b599 100644
      --- a/slack_sdk/version.py
      +++ b/slack_sdk/version.py
      @@ -1,3 +1,3 @@
       """Check the latest version at https://pypi.org/project/slack-sdk/"""
       
      -__version__ = "3.40.0"
      +__version__ = "3.40.1"