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 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. diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 000000000..2cdb5b9d5 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,184 @@ +name: Python CI + +on: + push: + branches: + - main + pull_request: + schedule: + - 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@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 }} + - name: Run lint verification + run: ./scripts/lint.sh + + typecheck: + name: Typecheck + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + 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 }} + - name: Run mypy verification + run: ./scripts/run_mypy.sh + + unittest: + name: Unit tests + runs-on: ubuntu-22.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: + - "3.14" + - "3.13" + - "3.12" + - "3.11" + - "3.10" + - "3.9" + - "3.8" + - "3.7" + - "pypy3.10" + - "pypy3.11" + permissions: + contents: read + env: + CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" + FORCE_COLOR: "1" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install dependencies + run: | + pip install -U pip + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt + - name: Run tests + run: | + 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: | + # Install v1.4 for testing + pip install "SQLAlchemy>=1.4,<2" + PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py + 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@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + directory: ./reports/ + fail_ci_if_error: true + flags: ${{ matrix.python-version }} + report_type: test_results + token: ${{ secrets.CODECOV_TOKEN }} + 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@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + fail_ci_if_error: true + # Run validation generates the coverage file + files: ./coverage.xml + report_type: coverage + 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 + needs: + - lint + - typecheck + - unittest + - databases + if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} + steps: + - name: Send notifications of failing tests + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + errors: true + webhook: ${{ secrets.SLACK_REGRESSION_FAILURES_WEBHOOK_URL }} + webhook-type: webhook-trigger + payload: | + action_url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + repository: "${{ github.repository }}" 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 }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 21b472247..9c9003c92 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -18,13 +18,13 @@ jobs: contents: read steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.release.tag_name || github.ref }} persist-credentials: false - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.x" @@ -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/ @@ -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/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 92ed007c8..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,117 +0,0 @@ -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Test - -on: - push: - branches: - - main - pull_request: - schedule: - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - typecheck: - name: Typechecks - runs-on: ubuntu-latest - timeout-minutes: 5 - strategy: - matrix: - python-version: ["3.14"] - permissions: - contents: read - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: ${{ matrix.python-version }} - - name: Run mypy verification - run: | - ./scripts/run_mypy.sh - unittest: - name: Unit tests - runs-on: ubuntu-22.04 - timeout-minutes: 15 - strategy: - fail-fast: false - matrix: - python-version: - - "3.14" - - "3.13" - - "3.12" - - "3.11" - - "3.10" - - "3.9" - - "3.8" - - "3.7" - - "pypy3.10" - permissions: - contents: read - env: - CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" - FORCE_COLOR: "1" - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - name: Install dependencies - run: | - pip install -U pip - pip install -r requirements/testing.txt - pip install -r requirements/optional.txt - - name: Run validation (black/flake8/pytest) - 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: | - # Install v1.4 for testing - pip install "SQLAlchemy>=1.4,<2" - PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py - 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 - with: - directory: ./reports/ - fail_ci_if_error: true - flags: ${{ matrix.python-version }} - report_type: test_results - token: ${{ secrets.CODECOV_TOKEN }} - verbose: true - - name: Upload test coverage to Codecov (only with latest supported version) - if: startsWith(matrix.python-version, '3.14') - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 - with: - fail_ci_if_error: true - # Run validation generates the coverage file - files: ./coverage.xml - report_type: coverage - token: ${{ secrets.CODECOV_TOKEN }} - verbose: true - notifications: - name: Regression notifications - runs-on: ubuntu-latest - needs: - - typecheck - - unittest - if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} - steps: - - name: Send notifications of failing tests - uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 - with: - errors: true - webhook: ${{ secrets.SLACK_REGRESSION_FAILURES_WEBHOOK_URL }} - webhook-type: webhook-trigger - payload: | - action_url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - repository: "${{ github.repository }}" 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 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 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.] 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..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) @@ -2894,7 +2897,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: @@ -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, ) @@ -3123,6 +3137,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 +3154,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 +3786,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 +5064,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, *, @@ -9979,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
@@ -9991,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. @@ -10002,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)
@@ -10170,7 +10456,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 +10482,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: @@ -10329,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
@@ -10344,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. @@ -10356,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)
@@ -10365,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
@@ -10380,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. @@ -10392,6 +10684,7 @@

Methods

"markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10402,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
@@ -10417,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. @@ -10443,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: @@ -10468,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, ) @@ -10500,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.
@@ -10518,7 +10818,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 +10833,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 +10850,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 +11882,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 +13892,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)
+
+

Creates a List. +https://docs.slack.dev/reference/methods/slackLists.create

+
+
+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)
+
+

Initiate a job to export List contents. +https://docs.slack.dev/reference/methods/slackLists.download.start

+
+
+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)
+
+

Add a new item to an existing List. +https://docs.slack.dev/reference/methods/slackLists.items.create

+
+
+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)
+
+

Deletes an item from an existing List. +https://docs.slack.dev/reference/methods/slackLists.items.delete

+
+
+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)
+
+

Deletes multiple items from an existing List. +https://docs.slack.dev/reference/methods/slackLists.items.deleteMultiple

+
+
+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)
+
+

Get a row from a List. +https://docs.slack.dev/reference/methods/slackLists.items.info

+
+
+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)
+
+

Get records from a List. +https://docs.slack.dev/reference/methods/slackLists.items.list

+
+
+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)
+
+

Updates cells in a List. +https://docs.slack.dev/reference/methods/slackLists.items.update

+
+
+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)
+
+

Update a List. +https://docs.slack.dev/reference/methods/slackLists.update

+
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 +16244,7 @@

WebClientdnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15601,6 +16314,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..cda8d9c95 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: @@ -207,8 +212,42 @@

    Subclasses

  • AbstractDialogSelector
  • DialogBuilder
  • DialogTextComponent
  • +
  • Chunk
  • 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 +296,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 +314,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 +455,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/block_elements.html b/docs/reference/models/blocks/block_elements.html index 375ee07ce..880573706 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

    @@ -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 f266b5cad..af825e54a 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -236,6 +236,12 @@

      Inherited members

      return VideoBlock(**block) elif type == RichTextBlock.type: 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 @@ -267,8 +273,11 @@

      Subclasses

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

    Class variables

    @@ -1318,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: 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
    +
    +

    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
    +
    A sequence of task card blocks. Each task represents a single action within the plan.
    +
    +

    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)
    @@ -1580,6 +1698,264 @@

    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 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)
    @@ -1888,6 +2264,13 @@

    PlanBlock

    + + +
  • RichTextBlock

    Class variables

    @@ -5342,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: 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
    +
    +

    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
    +
    A sequence of task card blocks. Each task represents a single action within the plan.
    +
    +

    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)
    @@ -5462,6 +5581,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 +7195,264 @@

    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 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)
    @@ -7030,6 +7531,7 @@

    Subclasses

    Class variables

    @@ -7350,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)
    @@ -8144,6 +8743,13 @@

    PlanBlock

    + + +
  • RadioButtonsElement

    • attributes
    • @@ -8151,6 +8757,15 @@

      RawTextObject

      + + +
    • RichTextBlock

      • attributes
      • @@ -8248,6 +8863,20 @@

        TableBlock

        + + +
      • +

        TaskCardBlock

        + +
      • +
      • TextObject

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

          UrlSourceElement

          + + +
        • UserMultiSelectElement

          diff --git a/docs/reference/models/messages/chunk.html b/docs/reference/models/messages/chunk.html new file mode 100644 index 000000000..100f83fa4 --- /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 | ForwardRef('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/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/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/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/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 29fe36884..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.utcfromtimestamp(time.time() + self.expiration_seconds) + 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.utcnow())) + 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.utcnow()))
    +            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.utcfromtimestamp(time.time() + self.expiration_seconds)
    +    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.utcfromtimestamp(time.time() + self.expiration_seconds) + 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.utcnow())) + 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.utcnow()))
    +            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.utcfromtimestamp(time.time() + self.expiration_seconds)
    +    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/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 6e9c712fe..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) @@ -2790,7 +2793,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: @@ -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, ) @@ -3019,6 +3033,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 +3050,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 +3682,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 +4960,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, *, @@ -9875,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
    @@ -9887,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. @@ -9898,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)
    @@ -10066,7 +10352,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 +10378,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: @@ -10225,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
    @@ -10240,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. @@ -10252,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)
    @@ -10261,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
    @@ -10276,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. @@ -10288,6 +10580,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10298,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
    @@ -10313,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. @@ -10339,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: @@ -10364,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, )
    @@ -10396,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.
    @@ -10414,7 +10714,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 +10729,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 +10746,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 +11778,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 +13788,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 +15680,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15037,6 +15750,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/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 6be773f23..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) @@ -2790,7 +2793,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: @@ -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, ) @@ -3019,6 +3033,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 +3050,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 +3682,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 +4960,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, *, @@ -9875,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
    @@ -9887,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. @@ -9898,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)
    @@ -10066,7 +10352,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 +10378,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: @@ -10225,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
    @@ -10240,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. @@ -10252,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)
    @@ -10261,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
    @@ -10276,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. @@ -10288,6 +10580,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10298,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
    @@ -10313,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. @@ -10339,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: @@ -10364,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, ) @@ -10396,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.
    @@ -10414,7 +10714,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 +10729,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 +10746,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 +11778,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 +13788,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 +15679,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15036,6 +15749,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..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) @@ -3159,7 +3162,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: @@ -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, ) @@ -3388,6 +3402,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 +3419,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 +4051,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 +5329,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, *, @@ -10244,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
    @@ -10256,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. @@ -10267,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)
    @@ -10435,7 +10721,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 +10747,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: @@ -10594,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
    @@ -10609,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. @@ -10621,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)
    @@ -10630,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
    @@ -10645,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. @@ -10657,6 +10949,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10667,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
    @@ -10682,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. @@ -10708,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: @@ -10733,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, ) @@ -10765,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.
    @@ -10783,7 +11083,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 +11098,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 +11115,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 +12147,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 +14157,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 +16074,7 @@

    Web
  • dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -15431,6 +16144,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..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) @@ -2789,7 +2792,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]: @@ -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) @@ -2955,6 +2965,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 +2982,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 +3614,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 +4892,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, *, @@ -9811,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
    @@ -9823,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. @@ -9834,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)
    @@ -10002,7 +10284,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 +10310,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]: @@ -10161,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
    @@ -10176,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. @@ -10188,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)
    @@ -10197,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
    @@ -10212,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. @@ -10224,6 +10512,7 @@

    Methods

    "markdown_text": markdown_text, "blocks": blocks, "metadata": metadata, + "chunks": chunks, } ) _parse_web_class_objects(kwargs) @@ -10234,7 +10523,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 +10538,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 +10555,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 +11587,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 +13597,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 +15486,7 @@

    dnd_setSnooze
  • dnd_teamInfo
  • emoji_list
  • +
  • entity_presentDetails
  • files_comments_delete
  • files_completeUploadExternal
  • files_delete
  • @@ -14854,6 +15556,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/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/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/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/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/requirements/documentation.txt b/requirements/documentation.txt index af8c47639..57304cb53 100644 --- a/requirements/documentation.txt +++ b/requirements/documentation.txt @@ -1,2 +1,2 @@ -docutils==0.22.2 +docutils==0.22.4 pdoc3==0.11.6 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 0a646dc12..1b43f52da 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,7 +1,7 @@ -mypy<=1.18.2 +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 # 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; 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 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/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index 334f55c40..b8592c2e9 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 ( @@ -54,6 +55,7 @@ StaticSelectElement, TimePickerElement, UrlInputElement, + UrlSourceElement, UserMultiSelectElement, UserSelectElement, ) @@ -69,8 +71,11 @@ ImageBlock, InputBlock, MarkdownBlock, + PlanBlock, RichTextBlock, SectionBlock, + TableBlock, + TaskCardBlock, VideoBlock, ) @@ -83,6 +88,7 @@ "Option", "OptionGroup", "PlainTextObject", + "RawTextObject", "TextObject", "BlockElement", "ButtonElement", @@ -108,6 +114,7 @@ "PlainTextInputElement", "EmailInputElement", "UrlInputElement", + "UrlSourceElement", "NumberInputElement", "RadioButtonsElement", "SelectElement", @@ -132,7 +139,10 @@ "ImageBlock", "InputBlock", "MarkdownBlock", + "PlanBlock", "SectionBlock", + "TableBlock", + "TaskCardBlock", "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/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 9976de5b4..bcd7efd6e 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -16,6 +16,7 @@ InputInteractiveElement, InteractiveElement, RichTextElement, + UrlSourceElement, ) # ------------------------------------------------- @@ -95,6 +96,12 @@ 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) + 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 @@ -731,3 +738,143 @@ 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 + + +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/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/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/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 b26f642cd..3898c5b32 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 @@ -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.utcfromtimestamp(time.time() + self.expiration_seconds) + 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.utcnow())) + 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.utcfromtimestamp(time.time() + self.expiration_seconds) + 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.utcnow())) + 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/slack_sdk/version.py b/slack_sdk/version.py index c787aa278..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.38.0" +__version__ = "3.40.1" 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 7b82a0923..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 -from ..models.metadata import Metadata +from ..models.blocks import Block, RichTextBlock +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) @@ -2769,7 +2773,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: @@ -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, ) @@ -2998,6 +3013,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 +3030,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 +3662,30 @@ 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[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, *, @@ -4899,6 +4940,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/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 8410ffee2..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 -from ..models.metadata import Metadata +from ..models.blocks import Block, RichTextBlock +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) @@ -2759,7 +2763,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: @@ -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, ) @@ -2988,6 +3003,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 +3020,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 +3652,30 @@ 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[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, *, @@ -4889,6 +4930,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/internal_utils.py b/slack_sdk/web/internal_utils.py index fadda929f..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 +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,13 +188,19 @@ 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, 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): + return obj.to_dict() + if isinstance(obj, EntityMetadata): + return obj.to_dict() return obj for blocks_name in ["blocks", "user_auth_blocks"]: @@ -207,8 +214,17 @@ def to_dict(obj: Union[Dict, Block, Attachment, Metadata]): 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): + 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 88c0fcf1a..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 -from ..models.metadata import Metadata +from ..models.blocks import Block, RichTextBlock +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) @@ -2770,7 +2774,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]: @@ -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) @@ -2936,6 +2947,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 +2964,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 +3596,30 @@ 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[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, *, @@ -4837,6 +4874,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/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index a07ce11b8..531ebe057 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -21,6 +21,8 @@ Option, OverflowMenuElement, PlainTextObject, + PlanBlock, + RawTextObject, RichTextBlock, RichTextElementParts, RichTextListElement, @@ -29,6 +31,8 @@ RichTextSectionElement, SectionBlock, StaticSelectElement, + TableBlock, + TaskCardBlock, VideoBlock, ) from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile @@ -888,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 # ---------------------------------------------- @@ -1267,3 +1351,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()) 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/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]}) 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_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) 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) 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( 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")