├── .flake8 ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── 01_question.md │ ├── 02_enhancement.md │ ├── 03_document.md │ ├── 04_bug.md │ └── config.yml ├── contributing.md ├── dependabot.yml ├── issue_template.md ├── maintainers_guide.md ├── pull_request_template.md └── workflows │ ├── ci-build.yml │ ├── docs-deploy.yml │ ├── mypy.yml │ └── triage-issues.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── MANIFEST.in ├── README.md ├── codecov.yml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── content │ ├── audit-logs.md │ ├── index.md │ ├── installation.md │ ├── legacy │ │ ├── auth.md │ │ ├── basic_usage.md │ │ ├── changelog.md │ │ ├── conversations.md │ │ ├── faq.md │ │ ├── index.md │ │ └── real_time_messaging.md │ ├── oauth.md │ ├── rtm.md │ ├── scim.md │ ├── socket-mode.md │ ├── tutorial │ │ ├── understanding-oauth-scopes.md │ │ └── uploading-files.md │ ├── v3-migration.md │ ├── web.md │ └── webhook.md ├── docusaurus.config.js ├── footerConfig.js ├── navbarConfig.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── css │ │ └── custom.css │ └── theme │ │ └── NotFound │ │ ├── Content │ │ └── index.js │ │ └── index.js └── static │ ├── api-docs │ └── slack_sdk │ │ ├── aiohttp_version_checker.html │ │ ├── audit_logs │ │ ├── async_client.html │ │ ├── index.html │ │ └── v1 │ │ │ ├── async_client.html │ │ │ ├── client.html │ │ │ ├── index.html │ │ │ ├── internal_utils.html │ │ │ ├── logs.html │ │ │ └── response.html │ │ ├── errors │ │ └── index.html │ │ ├── http_retry │ │ ├── async_handler.html │ │ ├── builtin_async_handlers.html │ │ ├── builtin_handlers.html │ │ ├── builtin_interval_calculators.html │ │ ├── handler.html │ │ ├── index.html │ │ ├── interval_calculator.html │ │ ├── jitter.html │ │ ├── request.html │ │ ├── response.html │ │ └── state.html │ │ ├── index.html │ │ ├── models │ │ ├── attachments │ │ │ └── index.html │ │ ├── basic_objects.html │ │ ├── blocks │ │ │ ├── basic_components.html │ │ │ ├── block_elements.html │ │ │ ├── blocks.html │ │ │ └── index.html │ │ ├── dialoags.html │ │ ├── dialogs │ │ │ └── index.html │ │ ├── index.html │ │ ├── messages │ │ │ ├── index.html │ │ │ └── message.html │ │ ├── metadata │ │ │ └── index.html │ │ └── views │ │ │ └── index.html │ │ ├── oauth │ │ ├── authorize_url_generator │ │ │ └── index.html │ │ ├── index.html │ │ ├── installation_store │ │ │ ├── amazon_s3 │ │ │ │ └── index.html │ │ │ ├── async_cacheable_installation_store.html │ │ │ ├── async_installation_store.html │ │ │ ├── cacheable_installation_store.html │ │ │ ├── file │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── installation_store.html │ │ │ ├── internals.html │ │ │ ├── models │ │ │ │ ├── bot.html │ │ │ │ ├── index.html │ │ │ │ └── installation.html │ │ │ ├── sqlalchemy │ │ │ │ └── index.html │ │ │ └── sqlite3 │ │ │ │ └── index.html │ │ ├── redirect_uri_page_renderer │ │ │ └── index.html │ │ ├── state_store │ │ │ ├── amazon_s3 │ │ │ │ └── index.html │ │ │ ├── async_state_store.html │ │ │ ├── file │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── sqlalchemy │ │ │ │ └── index.html │ │ │ ├── sqlite3 │ │ │ │ └── index.html │ │ │ └── state_store.html │ │ ├── state_utils │ │ │ └── index.html │ │ └── token_rotation │ │ │ ├── async_rotator.html │ │ │ ├── index.html │ │ │ └── rotator.html │ │ ├── proxy_env_variable_loader.html │ │ ├── rtm │ │ ├── index.html │ │ └── v2 │ │ │ └── index.html │ │ ├── rtm_v2 │ │ └── index.html │ │ ├── scim │ │ ├── async_client.html │ │ ├── index.html │ │ └── v1 │ │ │ ├── async_client.html │ │ │ ├── client.html │ │ │ ├── default_arg.html │ │ │ ├── group.html │ │ │ ├── index.html │ │ │ ├── internal_utils.html │ │ │ ├── response.html │ │ │ ├── types.html │ │ │ └── user.html │ │ ├── signature │ │ └── index.html │ │ ├── socket_mode │ │ ├── aiohttp │ │ │ └── index.html │ │ ├── async_client.html │ │ ├── async_listeners.html │ │ ├── builtin │ │ │ ├── client.html │ │ │ ├── connection.html │ │ │ ├── frame_header.html │ │ │ ├── index.html │ │ │ └── internals.html │ │ ├── client.html │ │ ├── index.html │ │ ├── interval_runner.html │ │ ├── listeners.html │ │ ├── logger │ │ │ ├── index.html │ │ │ └── messages.html │ │ ├── request.html │ │ ├── response.html │ │ ├── websocket_client │ │ │ └── index.html │ │ └── websockets │ │ │ └── index.html │ │ ├── version.html │ │ ├── web │ │ ├── async_base_client.html │ │ ├── async_client.html │ │ ├── async_internal_utils.html │ │ ├── async_slack_response.html │ │ ├── base_client.html │ │ ├── client.html │ │ ├── deprecation.html │ │ ├── file_upload_v2_result.html │ │ ├── index.html │ │ ├── internal_utils.html │ │ ├── legacy_base_client.html │ │ ├── legacy_client.html │ │ ├── legacy_slack_response.html │ │ └── slack_response.html │ │ └── webhook │ │ ├── async_client.html │ │ ├── client.html │ │ ├── index.html │ │ ├── internal_utils.html │ │ └── webhook_response.html │ └── img │ ├── bolt-logo.svg │ ├── bolt-py-logo.svg │ ├── favicon.ico │ ├── slack-logo-on-white.png │ ├── slack-logo.svg │ ├── understanding-oauth-approve.png │ ├── understanding-oauth-flow.png │ ├── upload-files-allow.png │ ├── upload-files-bot-token.png │ ├── upload-files-delete.png │ ├── upload-files-first-upload.png │ ├── upload-files-install.png │ ├── upload-files-invite-bot.gif │ ├── upload-files-local-file.png │ └── upload-files-with-channel.png ├── integration_tests ├── audit_logs │ ├── test_async_client.py │ ├── test_client.py │ └── test_pagination.py ├── env_variable_names.py ├── helpers.py ├── rtm │ ├── test_issue_530.py │ ├── test_issue_558.py │ ├── test_issue_569.py │ ├── test_issue_605.py │ ├── test_issue_611.py │ ├── test_issue_631.py │ ├── test_issue_701.py │ └── test_rtm_client.py ├── samples │ ├── basic_usage │ │ ├── calling_any_api_methods.py │ │ ├── channels.py │ │ ├── emoji_reactions.py │ │ ├── rate_limits.py │ │ ├── sending_a_message.py │ │ ├── uploading_files.py │ │ ├── users.py │ │ ├── views.py │ │ ├── views_2.py │ │ └── views_default_to_current_conversation.py │ ├── conversations │ │ ├── create_private_channel.py │ │ ├── list_conversations.py │ │ └── open_dm.py │ ├── issues │ │ ├── issue_497.py │ │ ├── issue_506.py │ │ ├── issue_522.py │ │ ├── issue_690.py │ │ ├── issue_714.py │ │ ├── issue_838.py │ │ ├── issue_868.py │ │ └── issue_926.py │ ├── oauth │ │ ├── oauth_v2.py │ │ └── oauth_v2_async.py │ ├── openid_connect │ │ ├── app_manifest.yml │ │ ├── flask_example.py │ │ ├── requirements.txt │ │ └── sanic_example.py │ ├── readme │ │ ├── async_function_in_framework.py │ │ ├── async_script.py │ │ ├── proxy.py │ │ ├── rtm_client_basics.py │ │ ├── sending_messages.py │ │ └── uploading_files.py │ ├── rtm_v2 │ │ ├── rtm_v2_app.py │ │ └── rtm_v2_proxy_app.py │ ├── scim │ │ ├── search_groups.py │ │ ├── search_groups_async.py │ │ └── search_users.py │ ├── socket_mode │ │ ├── __init__.py │ │ ├── aiohttp_example.py │ │ ├── bolt_adapter │ │ │ ├── __init__.py │ │ │ ├── aiohttp.py │ │ │ ├── async_base_handler.py │ │ │ ├── async_internals.py │ │ │ ├── base_handler.py │ │ │ ├── builtin.py │ │ │ ├── internals.py │ │ │ ├── websocket_client.py │ │ │ └── websockets.py │ │ ├── bolt_aiohttp_async_example.py │ │ ├── bolt_aiohttp_example.py │ │ ├── bolt_builtin_example.py │ │ ├── bolt_oauth_aiohttp_async_example.py │ │ ├── bolt_oauth_aiohttp_example.py │ │ ├── bolt_oauth_builtin_example.py │ │ ├── bolt_oauth_websocket_client_example.py │ │ ├── bolt_websocket_client_example.py │ │ ├── bolt_websockets_async_example.py │ │ ├── bolt_websockets_example.py │ │ ├── builtin_example.py │ │ ├── builtin_proxy_auth_example.py │ │ ├── builtin_proxy_env_variable_example.py │ │ ├── builtin_proxy_example.py │ │ ├── websocket_client_example.py │ │ ├── websocket_client_proxy_example.py │ │ └── websockets_example.py │ ├── token_rotation │ │ ├── .gitignore │ │ ├── oauth.py │ │ ├── oauth_async.py │ │ ├── oauth_sqlalchemy.py │ │ ├── oauth_sqlite3.py │ │ └── util.py │ └── workflows │ │ └── steps_from_apps.py ├── scim │ ├── test_scim_client_read.py │ └── test_scim_client_write.py ├── web │ ├── test_admin_analytics.py │ ├── test_admin_auth_policy.py │ ├── test_admin_barriers.py │ ├── test_admin_conversations.py │ ├── test_admin_conversations_bulk.py │ ├── test_admin_conversations_restrictAccess.py │ ├── test_admin_conversations_retention.py │ ├── test_admin_rate_limit_retries.py │ ├── test_admin_roles.py │ ├── test_admin_usergroups.py │ ├── test_admin_users.py │ ├── test_admin_users_session.py │ ├── test_admin_users_session_settings.py │ ├── test_admin_users_unsupportedVersions_export.py │ ├── test_app_manifest.py │ ├── test_async_web_client.py │ ├── test_bookmarks.py │ ├── test_calls.py │ ├── test_canvases.py │ ├── test_conversations_connect.py │ ├── test_files_upload_v2.py │ ├── test_issue_1053.py │ ├── test_issue_1143.py │ ├── test_issue_1305.py │ ├── test_issue_378.py │ ├── test_issue_480.py │ ├── test_issue_560.py │ ├── test_issue_594.py │ ├── test_issue_654.py │ ├── test_issue_670.py │ ├── test_issue_672.py │ ├── test_issue_677.py │ ├── test_issue_714.py │ ├── test_issue_728.py │ ├── test_issue_770.py │ ├── test_issue_809.py │ ├── test_message_metadata.py │ ├── test_remote_file_replacement.py │ ├── test_team.py │ └── test_web_client.py └── webhook │ ├── test_async_webhook.py │ └── test_webhook.py ├── logs └── .gitkeep ├── pyproject.toml ├── requirements ├── documentation.txt ├── optional.txt └── testing.txt ├── scripts ├── build_pypi_package.sh ├── codegen.py ├── deploy_to_prod_pypi_org.sh ├── deploy_to_test_pypi_org.sh ├── generate_api_docs.sh ├── run_integration_tests.sh ├── run_mypy.sh ├── run_unit_tests.sh ├── run_validation.sh └── uninstall_all.sh ├── setup.cfg ├── slack ├── __init__.py ├── deprecation.py ├── errors.py ├── py.typed ├── rtm │ ├── __init__.py │ └── client.py ├── signature │ ├── __init__.py │ └── verifier.py ├── version.py ├── web │ ├── __init__.py │ ├── async_base_client.py │ ├── async_client.py │ ├── async_internal_utils.py │ ├── async_slack_response.py │ ├── base_client.py │ ├── classes │ │ ├── __init__.py │ │ ├── actions.py │ │ ├── attachments.py │ │ ├── blocks.py │ │ ├── dialog_elements.py │ │ ├── dialogs.py │ │ ├── elements.py │ │ ├── interactions.py │ │ ├── messages.py │ │ ├── objects.py │ │ ├── readme.md │ │ └── views.py │ ├── client.py │ ├── deprecation.py │ ├── internal_utils.py │ └── slack_response.py └── webhook │ ├── __init__.py │ ├── async_client.py │ ├── client.py │ ├── internal_utils.py │ └── webhook_response.py ├── slack_sdk ├── __init__.py ├── aiohttp_version_checker.py ├── audit_logs │ ├── __init__.py │ ├── async_client.py │ └── v1 │ │ ├── __init__.py │ │ ├── async_client.py │ │ ├── client.py │ │ ├── internal_utils.py │ │ ├── logs.py │ │ └── response.py ├── errors │ └── __init__.py ├── http_retry │ ├── __init__.py │ ├── async_handler.py │ ├── builtin_async_handlers.py │ ├── builtin_handlers.py │ ├── builtin_interval_calculators.py │ ├── handler.py │ ├── interval_calculator.py │ ├── jitter.py │ ├── request.py │ ├── response.py │ └── state.py ├── models │ ├── __init__.py │ ├── attachments │ │ └── __init__.py │ ├── basic_objects.py │ ├── blocks │ │ ├── __init__.py │ │ ├── basic_components.py │ │ ├── block_elements.py │ │ └── blocks.py │ ├── dialoags.py │ ├── dialogs │ │ └── __init__.py │ ├── messages │ │ ├── __init__.py │ │ └── message.py │ ├── metadata │ │ └── __init__.py │ └── views │ │ └── __init__.py ├── oauth │ ├── __init__.py │ ├── authorize_url_generator │ │ └── __init__.py │ ├── installation_store │ │ ├── __init__.py │ │ ├── amazon_s3 │ │ │ └── __init__.py │ │ ├── async_cacheable_installation_store.py │ │ ├── async_installation_store.py │ │ ├── cacheable_installation_store.py │ │ ├── file │ │ │ └── __init__.py │ │ ├── installation_store.py │ │ ├── internals.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── bot.py │ │ │ └── installation.py │ │ ├── sqlalchemy │ │ │ └── __init__.py │ │ └── sqlite3 │ │ │ └── __init__.py │ ├── redirect_uri_page_renderer │ │ └── __init__.py │ ├── state_store │ │ ├── __init__.py │ │ ├── amazon_s3 │ │ │ └── __init__.py │ │ ├── async_state_store.py │ │ ├── file │ │ │ └── __init__.py │ │ ├── sqlalchemy │ │ │ └── __init__.py │ │ ├── sqlite3 │ │ │ └── __init__.py │ │ └── state_store.py │ ├── state_utils │ │ └── __init__.py │ └── token_rotation │ │ ├── __init__.py │ │ ├── async_rotator.py │ │ └── rotator.py ├── proxy_env_variable_loader.py ├── py.typed ├── rtm │ ├── __init__.py │ └── v2 │ │ └── __init__.py ├── rtm_v2 │ └── __init__.py ├── scim │ ├── __init__.py │ ├── async_client.py │ └── v1 │ │ ├── __init__.py │ │ ├── async_client.py │ │ ├── client.py │ │ ├── default_arg.py │ │ ├── group.py │ │ ├── internal_utils.py │ │ ├── response.py │ │ ├── types.py │ │ └── user.py ├── signature │ └── __init__.py ├── socket_mode │ ├── __init__.py │ ├── aiohttp │ │ └── __init__.py │ ├── async_client.py │ ├── async_listeners.py │ ├── builtin │ │ ├── __init__.py │ │ ├── client.py │ │ ├── connection.py │ │ ├── frame_header.py │ │ └── internals.py │ ├── client.py │ ├── interval_runner.py │ ├── listeners.py │ ├── logger │ │ ├── __init__.py │ │ └── messages.py │ ├── request.py │ ├── response.py │ ├── websocket_client │ │ └── __init__.py │ └── websockets │ │ └── __init__.py ├── version.py ├── web │ ├── __init__.py │ ├── async_base_client.py │ ├── async_client.py │ ├── async_internal_utils.py │ ├── async_slack_response.py │ ├── base_client.py │ ├── client.py │ ├── deprecation.py │ ├── file_upload_v2_result.py │ ├── internal_utils.py │ ├── legacy_base_client.py │ ├── legacy_client.py │ ├── legacy_slack_response.py │ └── slack_response.py └── webhook │ ├── __init__.py │ ├── async_client.py │ ├── client.py │ ├── internal_utils.py │ └── webhook_response.py ├── tests ├── __init__.py ├── data │ ├── channel.created.json │ ├── im.created.json │ ├── rtm.start.json │ ├── slack_logo.png │ ├── slack_logo_new.png │ ├── view_home_001.json │ ├── view_home_002.json │ ├── view_home_003.json │ ├── view_home_004.json │ ├── view_home_005.json │ ├── view_home_006.json │ ├── view_modal_001.json │ ├── view_modal_002.json │ ├── view_modal_003.json │ ├── view_modal_004.json │ ├── view_modal_005.json │ ├── view_modal_006.json │ ├── view_modal_007.json │ ├── view_modal_008.json │ ├── view_modal_009.json │ ├── view_modal_010.json │ ├── web_response_api_test.json │ ├── web_response_api_test_false.json │ ├── web_response_channels_list_pagination.json │ ├── web_response_channels_list_pagination2.json │ ├── web_response_channels_list_pagination2_page2.json │ ├── web_response_channels_list_pagination_has_page2.json │ ├── web_response_channels_list_pagination_has_page3.json │ ├── web_response_conversations_list.json │ ├── web_response_users_list_pagination.json │ ├── web_response_users_list_pagination_1.json │ ├── web_response_users_setPhoto.json │ └── 日本語.txt ├── helpers.py ├── mock_web_api_server │ ├── __init__.py │ ├── mock_server_thread.py │ └── received_requests.py ├── rtm │ ├── mock_web_api_server.py │ ├── test_rtm_client.py │ ├── test_rtm_client_functional.py │ └── test_rtm_client_v2.py ├── signature │ └── test_signature_verifier.py ├── slack_sdk │ ├── __init__.py │ ├── audit_logs │ │ ├── __init__.py │ │ ├── mock_web_api_handler.py │ │ ├── test_client.py │ │ ├── test_client_http_retry.py │ │ └── test_response.py │ ├── fatal_error_retry_handler.py │ ├── http_retry │ │ ├── __init__.py │ │ └── test_builtins.py │ ├── models │ │ ├── __init__.py │ │ ├── test_actions.py │ │ ├── test_attachments.py │ │ ├── test_blocks.py │ │ ├── test_dialoags.py │ │ ├── test_dialogs.py │ │ ├── test_elements.py │ │ ├── test_init.py │ │ ├── test_objects.py │ │ ├── test_options.py │ │ └── test_views.py │ ├── my_retry_handler.py │ ├── oauth │ │ ├── __init__.py │ │ ├── authorize_url_generator │ │ │ ├── __init__.py │ │ │ └── test_generator.py │ │ ├── installation_store │ │ │ ├── __init__.py │ │ │ ├── test_amazon_s3.py │ │ │ ├── test_async_sqlalchemy.py │ │ │ ├── test_file.py │ │ │ ├── test_interface.py │ │ │ ├── test_internals.py │ │ │ ├── test_models.py │ │ │ ├── test_simple_cache.py │ │ │ ├── test_sqlalchemy.py │ │ │ └── test_sqlite3.py │ │ ├── redirect_uri_page_renderer │ │ │ ├── __init__.py │ │ │ └── test_init.py │ │ ├── state_store │ │ │ ├── __init__.py │ │ │ ├── test_amazon_s3.py │ │ │ ├── test_async_sqlalchemy.py │ │ │ ├── test_file.py │ │ │ ├── test_sqlalchemy.py │ │ │ └── test_sqlite3.py │ │ ├── state_utils │ │ │ ├── __init__.py │ │ │ └── test_utils.py │ │ ├── test_init.py │ │ └── token_rotation │ │ │ ├── __init__.py │ │ │ └── test_token_rotator.py │ ├── rtm_v2 │ │ ├── __init__.py │ │ └── test_rtm_v2.py │ ├── scim │ │ ├── __init__.py │ │ ├── mock_web_api_handler.py │ │ ├── test_client.py │ │ ├── test_client_http_retry.py │ │ ├── test_internals.py │ │ └── test_response.py │ ├── signature │ │ ├── __init__.py │ │ └── test_signature_verifier.py │ ├── socket_mode │ │ ├── __init__.py │ │ ├── logger │ │ │ ├── __init__.py │ │ │ └── test_messages.py │ │ ├── mock_socket_mode_server.py │ │ ├── mock_web_api_handler.py │ │ ├── test_builtin.py │ │ ├── test_builtin_message_parser.py │ │ ├── test_interactions_builtin.py │ │ ├── test_interactions_websocket_client.py │ │ ├── test_request.py │ │ ├── test_response.py │ │ └── test_websocket_client.py │ ├── web │ │ ├── __init__.py │ │ ├── mock_web_api_handler.py │ │ ├── mock_web_api_http_retry_handler.py │ │ ├── test_internal_utils.py │ │ ├── test_legacy_web_client_url_format.py │ │ ├── test_slack_response.py │ │ ├── test_web_client.py │ │ ├── test_web_client_http_retry.py │ │ ├── test_web_client_http_retry_connection.py │ │ ├── test_web_client_http_retry_server_error.py │ │ ├── test_web_client_issue_1049.py │ │ ├── test_web_client_issue_829.py │ │ ├── test_web_client_issue_891.py │ │ ├── test_web_client_issue_900.py │ │ ├── test_web_client_issue_921_custom_logger.py │ │ ├── test_web_client_issue_971.py │ │ ├── test_web_client_logger.py │ │ ├── test_web_client_stars_deprecation.py │ │ ├── test_web_client_url_format.py │ │ └── test_web_client_workflow_step_deprecation.py │ └── webhook │ │ ├── __init__.py │ │ ├── mock_web_api_server.py │ │ ├── test_webhook.py │ │ └── test_webhook_http_retry.py ├── slack_sdk_async │ ├── __init__.py │ ├── audit_logs │ │ ├── __init__.py │ │ ├── test_async_client.py │ │ └── test_async_client_http_retry.py │ ├── fatal_error_retry_handler.py │ ├── helpers.py │ ├── http_retry │ │ ├── __init__.py │ │ └── test_builtins.py │ ├── my_retry_handler.py │ ├── oauth │ │ ├── __init__.py │ │ ├── installation_store │ │ │ ├── __init__.py │ │ │ └── test_simple_cache.py │ │ └── token_rotation │ │ │ ├── __init__.py │ │ │ └── test_token_rotator.py │ ├── scim │ │ ├── __init__.py │ │ ├── test_async_client.py │ │ └── test_async_client_http_retry.py │ ├── socket_mode │ │ ├── __init__.py │ │ ├── test_aiohttp.py │ │ ├── test_interactions_aiohttp.py │ │ ├── test_interactions_websockets.py │ │ └── test_websockets.py │ ├── web │ │ ├── __init__.py │ │ ├── test_async_slack_response.py │ │ ├── test_async_web_client.py │ │ ├── test_async_web_client_http_retry.py │ │ ├── test_async_web_client_logger.py │ │ ├── test_web_client_coverage.py │ │ ├── test_web_client_issue_829.py │ │ ├── test_web_client_issue_891.py │ │ ├── test_web_client_issue_921_custom_logger.py │ │ └── test_web_client_url_format.py │ └── webhook │ │ ├── __init__.py │ │ ├── test_async_webhook.py │ │ └── test_async_webhook_http_retry.py ├── slack_sdk_fixture │ ├── channel.created.json │ ├── im.created.json │ ├── rtm.start.json │ ├── slack_logo.png │ ├── slack_logo_new.png │ ├── view_home_001.json │ ├── view_home_002.json │ ├── view_home_003.json │ ├── view_home_004.json │ ├── view_home_005.json │ ├── view_home_006.json │ ├── view_modal_001.json │ ├── view_modal_002.json │ ├── view_modal_003.json │ ├── view_modal_004.json │ ├── view_modal_005.json │ ├── view_modal_006.json │ ├── view_modal_007.json │ ├── view_modal_008.json │ ├── view_modal_009.json │ ├── view_modal_010.json │ ├── web_response_admin_convo_pagination.json │ ├── web_response_admin_convo_pagination_1.json │ ├── web_response_api_test.json │ ├── web_response_api_test_false.json │ ├── web_response_conversations_list.json │ ├── web_response_conversations_list_pagination.json │ ├── web_response_conversations_list_pagination2.json │ ├── web_response_conversations_list_pagination2_page2.json │ ├── web_response_conversations_list_pagination_has_page2.json │ ├── web_response_conversations_list_pagination_has_page3.json │ ├── web_response_fatal_error_only_once.json │ ├── web_response_rate_limited_only_once.json │ ├── web_response_ratelimited_only_once.json │ ├── web_response_users_list_pagination.json │ ├── web_response_users_list_pagination_1.json │ ├── web_response_users_setPhoto.json │ └── 日本語.txt ├── test_aiohttp_version_checker.py ├── test_asyncio_event_loops.py ├── test_proxy_env_variable_loader.py ├── web │ ├── classes │ │ ├── __init__.py │ │ ├── test_actions.py │ │ ├── test_attachments.py │ │ ├── test_blocks.py │ │ ├── test_dialogs.py │ │ ├── test_elements.py │ │ ├── test_init.py │ │ ├── test_messages.py │ │ ├── test_objects.py │ │ └── test_views.py │ ├── mock_web_api_handler.py │ ├── test_async_web_client.py │ ├── test_slack_response.py │ ├── test_web_client.py │ ├── test_web_client_coverage.py │ ├── test_web_client_functional.py │ ├── test_web_client_issue_829.py │ ├── test_web_client_issue_891.py │ └── test_web_client_issue_921_custom_logger.py └── webhook │ ├── mock_web_api_handler.py │ ├── test_async_webhook.py │ └── test_webhook.py └── tutorial ├── 01-creating-the-slack-app.md ├── 02-building-a-message.md ├── 03-responding-to-slack-events.md ├── 04-running-the-app.md ├── PythOnBoardingBot ├── app.py ├── onboarding_tutorial.py └── requirements.txt ├── README.md └── assets ├── authorize-install.png ├── bot-token.png ├── enable-events.png ├── oauth-installation.png ├── oauth-permissions.png ├── signing-secret.png └── subscribe-events.png /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 125 -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # change black settings 2 | 108955b601e768fd56696be903fc8b471c73ebf7 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SDK Question 3 | about: Submit a question about this SDK 4 | title: (Set a clear title describing your question) 5 | labels: "untriaged" 6 | assignees: "" 7 | --- 8 | 9 | (Describe your issue and goal here) 10 | 11 | ### Reproducible in: 12 | 13 | ```bash 14 | pip freeze | grep slack 15 | python --version 16 | sw_vers && uname -v # or `ver` 17 | ``` 18 | 19 | #### The Slack SDK version 20 | 21 | (Paste the output of `pip freeze | grep slack`) 22 | 23 | #### Python runtime version 24 | 25 | (Paste the output of `python --version`) 26 | 27 | #### OS info 28 | 29 | (Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) 30 | 31 | #### Steps to reproduce: 32 | 33 | (Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 34 | 35 | 1. 36 | 2. 37 | 3. 38 | 39 | ### Expected result: 40 | 41 | (Tell what you expected to happen) 42 | 43 | ### Actual result: 44 | 45 | (Tell what actually happened with logs, screenshots) 46 | 47 | ### Requirements 48 | 49 | For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: 50 | 51 | Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SDK Enhancement / Feature Request 3 | about: Submit an enhancement/feature request 4 | title: (Set a clear title describing your idea) 5 | labels: "untriaged" 6 | assignees: "" 7 | --- 8 | 9 | (Describe your issue and goal here) 10 | 11 | ### Category (place an `x` in each of the `[ ]`) 12 | 13 | - [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client) 14 | - [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender) 15 | - [ ] **slack_sdk.models** (UI component builders) 16 | - [ ] **slack_sdk.oauth** (OAuth Flow Utilities) 17 | - [ ] **slack_sdk.socket_mode** (Socket Mode client) 18 | - [ ] **slack_sdk.audit_logs** (Audit Logs API client) 19 | - [ ] **slack_sdk.scim** (SCIM API client) 20 | - [ ] **slack_sdk.rtm** (RTM client) 21 | - [ ] **slack_sdk.signature** (Request Signature Verifier) 22 | 23 | ### Requirements 24 | 25 | Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_document.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SDK Document 3 | about: Submit an issue on documents 4 | title: (Set a clear title describing your idea) 5 | labels: "untriaged" 6 | assignees: "" 7 | --- 8 | 9 | (Describe your issue and goal here) 10 | 11 | ### The page URLs 12 | 13 | - https://slack.dev/python-slack-sdk/ 14 | 15 | ### Requirements 16 | 17 | Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04_bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SDK Bug 3 | about: Report the SDK bug 4 | title: (Set a clear title describing the issue) 5 | labels: "untriaged" 6 | assignees: "" 7 | --- 8 | 9 | (Filling out the following details about bugs will help us solve your issue sooner.) 10 | 11 | ### Reproducible in: 12 | 13 | ```bash 14 | pip freeze | grep slack 15 | python --version 16 | sw_vers && uname -v # or `ver` 17 | ``` 18 | 19 | #### The Slack SDK version 20 | 21 | (Paste the output of `pip freeze | grep slack`) 22 | 23 | #### Python runtime version 24 | 25 | (Paste the output of `python --version`) 26 | 27 | #### OS info 28 | 29 | (Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) 30 | 31 | #### Steps to reproduce: 32 | 33 | (Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 34 | 35 | 1. 36 | 2. 37 | 3. 38 | 39 | ### Expected result: 40 | 41 | (Tell what you expected to happen) 42 | 43 | ### Actual result: 44 | 45 | (Tell what actually happened with logs, screenshots) 46 | 47 | ### Requirements 48 | 49 | For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: 50 | 51 | Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Slack Platform Customer Support 4 | url: https://my.slack.com/help/requests/new 5 | about: | 6 | This issue tracker is a place to track bugs, feature requests, and questions on this SDK side. 7 | If you have a general question on how to use the Slack platform, please get in touch with our customer support agents first via either /feedback in your Slack workspace or the help page link here. 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "pip" 7 | directory: "/" 8 | schedule: 9 | interval: "monthly" 10 | open-pull-requests-limit: 5 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "monthly" 15 | - package-ecosystem: "npm" 16 | directory: "/docs" 17 | schedule: 18 | interval: "monthly" 19 | groups: 20 | docusaurus: 21 | patterns: 22 | - "@docusaurus/*" 23 | react: 24 | patterns: 25 | - "react" 26 | - "react-dom" 27 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | (Describe your issue and goal here) 2 | 3 | ### Reproducible in: 4 | 5 | ```bash 6 | pip freeze | grep slack 7 | python --version 8 | sw_vers && uname -v # or `ver` 9 | ``` 10 | 11 | #### The Slack SDK version 12 | 13 | (Paste the output of `pip freeze | grep slack`) 14 | 15 | #### Python runtime version 16 | 17 | (Paste the output of `python --version`) 18 | 19 | #### OS info 20 | 21 | (Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS) 22 | 23 | #### Steps to reproduce: 24 | 25 | (Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 26 | 27 | 1. 28 | 2. 29 | 3. 30 | 31 | ### Expected result: 32 | 33 | (Tell what you expected to happen) 34 | 35 | ### Actual result: 36 | 37 | (Tell what actually happened with logs, screenshots) 38 | 39 | ### Requirements 40 | 41 | For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow: 42 | 43 | Please read the [Contributing guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 44 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | ### Testing 6 | 7 | 8 | 9 | ### Category 10 | 11 | - [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client) 12 | - [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender) 13 | - [ ] **slack_sdk.socket_mode** (Socket Mode client) 14 | - [ ] **slack_sdk.signature** (Request Signature Verifier) 15 | - [ ] **slack_sdk.oauth** (OAuth Flow Utilities) 16 | - [ ] **slack_sdk.models** (UI component builders) 17 | - [ ] **slack_sdk.scim** (SCIM API client) 18 | - [ ] **slack_sdk.audit_logs** (Audit Logs API client) 19 | - [ ] **slack_sdk.rtm_v2** (RTM client) 20 | - [ ] `/docs` (Documents) 21 | - [ ] `/tutorial` (PythOnBoardingBot tutorial) 22 | - [ ] `tests`/`integration_tests` (Automated tests for this library) 23 | 24 | ## Requirements 25 | 26 | - [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and have done my best effort to follow them. 27 | - [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). 28 | - [ ] I've run `python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh` after making the changes. 29 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | name: mypy validation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | strategy: 14 | matrix: 15 | python-version: ["3.13"] 16 | permissions: 17 | contents: read 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | with: 21 | persist-credentials: false 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Run mypy verification 27 | run: | 28 | ./scripts/run_mypy.sh 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # general things to ignore 2 | build/ 3 | dist/ 4 | .eggs/ 5 | *.egg-info/ 6 | *.egg 7 | *.py[cod] 8 | __pycache__/ 9 | *.so 10 | *~ 11 | 12 | # virtualenv 13 | env*/ 14 | venv*/ 15 | .venv*/ 16 | .env*/ 17 | 18 | # codecov / coverage 19 | .coverage* 20 | cov_* 21 | coverage.xml 22 | reports/ 23 | 24 | # due to using tox and pytest 25 | .tox 26 | .cache 27 | .pytest_cache/ 28 | .python-version 29 | pip 30 | .mypy_cache/ 31 | 32 | # JetBrains PyCharm settings 33 | .idea/ 34 | 35 | tmp.txt 36 | .DS_Store 37 | logs/ 38 | 39 | .pytype/ 40 | *.db 41 | .env* 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "python.linting.pylintEnabled": false, 4 | "python.linting.flake8Enabled": true, 5 | "python.venvPath": "${workspaceFolder}/env", 6 | "python.formatting.provider": "black", 7 | "editor.formatOnSave": true, 8 | "python.linting.enabled": true, 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015- Slack Technologies, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE slack/py.typed slack_sdk/py.typed 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 2.0% 6 | patch: 7 | default: 8 | target: 50% 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .docusaurus 3 | build -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/footerConfig.js: -------------------------------------------------------------------------------- 1 | const footer = { 2 | links: [ 3 | { 4 | items: [ 5 | { 6 | html: ` 7 | 11 |
12 | ©2025 Slack Technologies, LLC, a Salesforce company. All rights reserved. Various trademarks held by their respective owners. 13 |
14 | `, 15 | }, 16 | ], 17 | }, 18 | ], 19 | }; 20 | 21 | module.exports = footer; 22 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "2024.08.01", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.7.0", 18 | "@docusaurus/plugin-client-redirects": "^3.7.0", 19 | "@docusaurus/preset-classic": "^3.7.0", 20 | "@mdx-js/react": "^3.1.0", 21 | "clsx": "^2.0.0", 22 | "docusaurus-theme-github-codeblock": "^2.0.2", 23 | "prism-react-renderer": "^2.4.1", 24 | "react": "^19.1.0", 25 | "react-dom": "^19.1.0" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "^3.5.2", 29 | "@docusaurus/types": "^3.5.2" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.5%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 3 chrome version", 39 | "last 3 firefox version", 40 | "last 5 safari version" 41 | ] 42 | }, 43 | "engines": { 44 | "node": ">=20.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/src/theme/NotFound/Content/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Translate from '@docusaurus/Translate'; 4 | import Heading from '@theme/Heading'; 5 | export default function NotFoundContent({className}) { 6 | return ( 7 |
8 |
9 |
10 | 11 | 14 | Oh no! There's nothing here. 15 | 16 | 17 |

18 | 21 | If we've led you astray, please let us know. We'll do our best to get things in order. 22 | 23 | 24 |

25 |

26 | 29 | For now, we suggest heading back to the beginning to get your bearings. May your next journey have clear skies to guide you true. 30 | 31 |

32 |
33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /docs/src/theme/NotFound/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {translate} from '@docusaurus/Translate'; 3 | import {PageMetadata} from '@docusaurus/theme-common'; 4 | import Layout from '@theme/Layout'; 5 | import NotFoundContent from '@theme/NotFound/Content'; 6 | export default function Index() { 7 | const title = translate({ 8 | id: 'theme.NotFound.title', 9 | message: 'Page Not Found', 10 | }); 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /docs/static/img/bolt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/bolt-py-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/slack-logo-on-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/slack-logo-on-white.png -------------------------------------------------------------------------------- /docs/static/img/slack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/static/img/understanding-oauth-approve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/understanding-oauth-approve.png -------------------------------------------------------------------------------- /docs/static/img/understanding-oauth-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/understanding-oauth-flow.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-allow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-allow.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-bot-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-bot-token.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-delete.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-first-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-first-upload.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-install.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-invite-bot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-invite-bot.gif -------------------------------------------------------------------------------- /docs/static/img/upload-files-local-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-local-file.png -------------------------------------------------------------------------------- /docs/static/img/upload-files-with-channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/docs/static/img/upload-files-with-channel.png -------------------------------------------------------------------------------- /integration_tests/audit_logs/test_pagination.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import unittest 4 | 5 | from integration_tests.env_variable_names import ( 6 | SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN, 7 | ) 8 | from slack_sdk.audit_logs import AuditLogsClient 9 | 10 | 11 | class TestAuditLogsClient(unittest.TestCase): 12 | def setUp(self): 13 | self.client = AuditLogsClient(token=os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN]) 14 | 15 | def tearDown(self): 16 | pass 17 | 18 | def test_pagination(self): 19 | call_count = 0 20 | response = None 21 | ids = [] 22 | while call_count < 10 and (response is None or response.status_code != 429): 23 | cursor = response.body["response_metadata"]["next_cursor"] if response is not None else None 24 | response = self.client.logs(action="user_login", limit=1, cursor=cursor) 25 | ids += map(lambda v: v["id"], response.body.get("entries", [])) 26 | call_count += 1 27 | self.assertGreaterEqual(len(set(ids)), 10) 28 | -------------------------------------------------------------------------------- /integration_tests/helpers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import inspect 3 | import sys 4 | from asyncio.events import AbstractEventLoop 5 | 6 | 7 | def async_test(coro): 8 | loop: AbstractEventLoop = asyncio.new_event_loop() 9 | asyncio.set_event_loop(loop) 10 | 11 | def wrapper(*args, **kwargs): 12 | current_loop: AbstractEventLoop = asyncio.get_event_loop() 13 | return current_loop.run_until_complete(coro(*args, **kwargs)) 14 | 15 | return wrapper 16 | 17 | 18 | def is_not_specified() -> bool: 19 | # get the caller's filepath 20 | frame = inspect.stack()[1] 21 | module = inspect.getmodule(frame[0]) 22 | filepath: str = module.__file__ 23 | 24 | # ./scripts/run_integration_tests.sh web/test_issue_560.py 25 | test_target: str = sys.argv[1] # e.g., web/test_issue_560.py 26 | return not test_target or not filepath.endswith(test_target) 27 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/calling_any_api_methods.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/basic_usage/calling_any_api_methods.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | response = client.api_call(api_method="chat.postMessage", json={"channel": "#random", "text": "Hello world!"}) 13 | assert response["message"]["text"] == "Hello world!" 14 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/channels.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/basic_usage/channels.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | 13 | response = client.conversations_list(exclude_archived=1) 14 | 15 | channel_id = response["channels"][0]["id"] 16 | 17 | response = client.conversations_info(channel=channel_id) 18 | 19 | response = client.conversations_join(channel=channel_id) 20 | 21 | response = client.conversations_leave(channel=channel_id) 22 | 23 | response = client.conversations_join(channel=channel_id) 24 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/emoji_reactions.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/basic_usage/emoji_reactions.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | 13 | if __name__ == "__main__": 14 | channel_id = "#random" 15 | user_id = client.users_list()["members"][0]["id"] 16 | else: 17 | channel_id = "C0XXXXXX" 18 | user_id = "U0XXXXXXX" 19 | 20 | response = client.chat_postMessage(channel=channel_id, text="Give me some reaction!") 21 | # Ensure the channel_id is not a name 22 | channel_id = response["channel"] 23 | ts = response["message"]["ts"] 24 | 25 | response = client.reactions_add(channel=channel_id, name="thumbsup", timestamp=ts) 26 | 27 | response = client.reactions_remove(channel=channel_id, name="thumbsup", timestamp=ts) 28 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/rate_limits.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/basic_usage/rate_limits.py 7 | 8 | import os 9 | import time 10 | from slack_sdk.web import WebClient 11 | from slack_sdk.errors import SlackApiError 12 | 13 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 14 | 15 | 16 | # Simple wrapper for sending a Slack message 17 | def send_slack_message(channel, message): 18 | return client.chat_postMessage(channel=channel, text=message) 19 | 20 | 21 | # Make the API call and save results to `response` 22 | channel = "#random" 23 | message = "Hello, from Python!" 24 | 25 | # Do until being rate limited 26 | while True: 27 | try: 28 | response = send_slack_message(channel, message) 29 | except SlackApiError as e: 30 | if e.response["error"] == "ratelimited": 31 | # The `Retry-After` header will tell you how long to wait before retrying 32 | delay = int(e.response.headers["Retry-After"]) 33 | print(f"Rate limited. Retrying in {delay} seconds") 34 | time.sleep(delay) 35 | response = send_slack_message(channel, message) 36 | else: 37 | # other errors 38 | raise e 39 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/uploading_files.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # echo 'Hello world!' > tmp.txt 7 | # python3 integration_tests/samples/basic_usage/uploading_files.py 8 | 9 | import os 10 | from slack_sdk.web import WebClient 11 | 12 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 13 | 14 | channels = ",".join(["#random"]) 15 | filepath = "./tmp.txt" 16 | response = client.files_upload(channels=channels, file=filepath) 17 | response = client.files_upload_v2(channel=response.get("file").get("channels")[0], file=filepath) 18 | -------------------------------------------------------------------------------- /integration_tests/samples/basic_usage/users.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/basic_usage/users.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | 13 | response = client.users_list() 14 | -------------------------------------------------------------------------------- /integration_tests/samples/conversations/create_private_channel.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/conversations/create_private_channel.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | from time import time 11 | 12 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 13 | 14 | channel_name = f"my-private-channel-{round(time())}" 15 | response = client.conversations_create(name=channel_name, is_private=True) 16 | channel_id = response["channel"]["id"] 17 | 18 | response = client.conversations_info(channel=channel_id, include_num_members=1) # TODO: True 19 | 20 | response = client.conversations_members(channel=channel_id) 21 | user_ids = response["members"] 22 | print(f"user_ids: {user_ids}") 23 | 24 | response = client.conversations_archive(channel=channel_id) 25 | -------------------------------------------------------------------------------- /integration_tests/samples/conversations/list_conversations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/conversations/list_conversations.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | 13 | response = client.conversations_list() 14 | 15 | response = client.conversations_list(types="public_channel, private_channel") 16 | 17 | channel_id = response["channels"][0]["id"] 18 | 19 | response = client.conversations_info(channel=channel_id, include_num_members=1) # TODO: True 20 | -------------------------------------------------------------------------------- /integration_tests/samples/conversations/open_dm.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/conversations/open_dm.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 12 | 13 | all_users = client.users_list(limit=100)["members"] 14 | joinable_only = ( 15 | lambda u: u["id"] != "USLACKBOT" 16 | and not u["is_bot"] 17 | and not u["is_app_user"] 18 | and not u["deleted"] 19 | and not u["is_restricted"] 20 | and not u["is_ultra_restricted"] 21 | ) 22 | users = filter(joinable_only, all_users) 23 | user_ids = list(map(lambda u: u["id"], users)) 24 | 25 | response = client.conversations_open(users=user_ids) 26 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_506.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/issues/issue_506.py 7 | 8 | import os 9 | from slack_sdk.rtm import RTMClient 10 | 11 | logger = logging.getLogger(__name__) 12 | global_state = {} 13 | 14 | 15 | @RTMClient.run_on(event="open") 16 | def open(**payload): 17 | web_client = payload["web_client"] 18 | auth_result = web_client.auth_test() 19 | global_state.update({"bot_id": auth_result["bot_id"]}) 20 | logger.info(f"cached: {global_state}") 21 | 22 | 23 | @RTMClient.run_on(event="message") 24 | def message(**payload): 25 | data = payload["data"] 26 | if data.get("bot_id", None) == global_state["bot_id"]: 27 | logger.debug("Skipped as it's me") 28 | return 29 | # do something here 30 | web_client = payload["web_client"] 31 | message = web_client.chat_postMessage(channel=data["channel"], text="What's up?") 32 | logger.info(f"message: {message['ts']}") 33 | 34 | 35 | rtm_client = RTMClient(token=os.environ["SLACK_API_TOKEN"]) 36 | rtm_client.start() 37 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_690.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # --------------------- 6 | # Flask App 7 | # --------------------- 8 | 9 | import os 10 | 11 | # pip install flask 12 | from flask import Flask, make_response, request 13 | 14 | app = Flask(__name__) 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | @app.route("/slack/oauth/callback", methods=["GET"]) 19 | def endpoint(): 20 | code = request.args.get("code") 21 | from slack_sdk.web import WebClient 22 | from slack_sdk.errors import SlackApiError 23 | 24 | try: 25 | client = WebClient(token="") 26 | client_id = os.environ["SLACK_CLIENT_ID"] 27 | client_secret = os.environ["SLACK_CLIENT_SECRET"] 28 | response = client.oauth_v2_access(client_id=client_id, client_secret=client_secret, code=code) 29 | result = response.get("error", "success!") 30 | return str(result) 31 | except SlackApiError as e: 32 | return make_response(str(e), 400) 33 | 34 | 35 | if __name__ == "__main__": 36 | # export SLACK_CLIENT_ID=111.222 37 | # export SLACK_CLIENT_SECRET= 38 | # FLASK_ENV=development python integration_tests/samples/issues/issue_690.py 39 | app.run(debug=True, host="localhost", port=3000) 40 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_714.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | 4 | logging.basicConfig(level=logging.DEBUG) 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | 11 | # export HTTPS_PROXY=http://localhost:9000 12 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 13 | response = client.auth_test() 14 | logger.info(f"HTTPS_PROXY response: {response}") 15 | 16 | client = WebClient(token=os.environ["SLACK_API_TOKEN"], proxy="http://localhost:9000") 17 | response = client.auth_test() 18 | logger.info(f"sync response: {response}") 19 | 20 | client = WebClient(token=os.environ["SLACK_API_TOKEN"], proxy="localhost:9000") 21 | response = client.auth_test() 22 | logger.info(f"sync response: {response}") 23 | 24 | 25 | async def async_call(): 26 | client = WebClient( 27 | token=os.environ["SLACK_API_TOKEN"], 28 | proxy="http://localhost:9000", 29 | run_async=True, 30 | ) 31 | response = await client.auth_test() 32 | logger.info(f"async response: {response}") 33 | 34 | 35 | asyncio.run(async_call()) 36 | 37 | # Terminal A: 38 | # pip3 install proxy.py 39 | # proxy --port 9000 --log-level d 40 | 41 | # Terminal B: 42 | # export SLACK_API_TOKEN=xoxb-*** 43 | # python3 integration_tests/samples/issues/issue_714.py 44 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_838.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | logging.basicConfig(level=logging.DEBUG) 5 | 6 | # --------------------- 7 | # Slack WebClient 8 | # --------------------- 9 | 10 | import os 11 | 12 | from slack_sdk.web import WebClient 13 | from slack_sdk.signature import SignatureVerifier 14 | 15 | app_token_client = WebClient(token=os.environ["SLACK_APP_TOKEN"]) # xapp- 16 | signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"]) 17 | 18 | # --------------------- 19 | # Flask App 20 | # --------------------- 21 | 22 | # pip3 install flask 23 | from flask import Flask, request, make_response 24 | 25 | app = Flask(__name__) 26 | 27 | 28 | @app.route("/slack/events", methods=["POST"]) 29 | def slack_app(): 30 | request_body = request.get_data() 31 | if not signature_verifier.is_valid_request(request_body, request.headers): 32 | return make_response("invalid request", 403) 33 | 34 | if request.headers["content-type"] == "application/json": 35 | body = json.loads(request_body) 36 | response = app_token_client.apps_event_authorizations_list(event_context=body["event_context"]) 37 | print(response) 38 | return make_response("", 200) 39 | 40 | return make_response("", 404) 41 | 42 | 43 | if __name__ == "__main__": 44 | # export SLACK_SIGNING_SECRET=*** 45 | # export SLACK_API_TOKEN=xoxb-*** 46 | # export FLASK_ENV=development 47 | # python3 integration_tests/web/test_issue_838.py 48 | app.run("localhost", 3000) 49 | 50 | # ngrok http 3000 51 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_868.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | 6 | def legacy(): 7 | from slack_sdk.models.blocks import SectionBlock 8 | from slack_sdk.models.blocks.basic_components import TextObject 9 | 10 | fields = [] 11 | fields.append(TextObject(text="...", type="mrkdwn")) 12 | block = SectionBlock(text="", fields=fields) 13 | assert block is not None 14 | 15 | 16 | from slack_sdk.models.blocks import SectionBlock, TextObject 17 | 18 | fields = [] 19 | fields.append(TextObject(text="...", type="mrkdwn")) 20 | block = SectionBlock(text="", fields=fields) 21 | assert block is not None 22 | 23 | # 24 | # pip install mypy 25 | # mypy integration_tests/samples/issues/issue_868.py | grep integration_tests 26 | # 27 | 28 | # integration_tests/samples/issues/issue_868.py:26: error: Argument "fields" to "SectionBlock" has incompatible type "List[TextObject]"; expected "Optional[List[Union[str, Dict[Any, Any], TextObject]]]" 29 | # integration_tests/samples/issues/issue_868.py:26: note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance 30 | # integration_tests/samples/issues/issue_868.py:26: note: Consider using "Sequence" instead, which is covariant 31 | -------------------------------------------------------------------------------- /integration_tests/samples/issues/issue_926.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | 5 | from slack_sdk.socket_mode.aiohttp import SocketModeClient 6 | 7 | logging.basicConfig(level=logging.DEBUG) 8 | 9 | 10 | async def main(): 11 | client = SocketModeClient(app_token=os.environ["SLACK_APP_TOKEN"]) 12 | await client.connect() 13 | await asyncio.sleep(3) 14 | await client.close() 15 | 16 | 17 | if __name__ == "__main__": 18 | asyncio.run(main()) 19 | 20 | # The issue: 21 | # ERROR:asyncio:Unclosed client session 22 | # client_session: 23 | # INFO:slack_sdk.socket_mode.aiohttp:The session has been abandoned 24 | -------------------------------------------------------------------------------- /integration_tests/samples/openid_connect/app_manifest.yml: -------------------------------------------------------------------------------- 1 | _metadata: 2 | major_version: 1 3 | minor_version: 1 4 | display_information: 5 | name: openid-connect-app 6 | features: 7 | app_home: 8 | home_tab_enabled: false 9 | messages_tab_enabled: true 10 | messages_tab_read_only_enabled: true 11 | oauth_config: 12 | redirect_urls: 13 | - https://{your-domain}/slack/oauth_redirect 14 | scopes: 15 | user: 16 | - openid 17 | - email 18 | - profile 19 | settings: 20 | org_deploy_enabled: false 21 | socket_mode_enabled: false 22 | token_rotation_enabled: false 23 | -------------------------------------------------------------------------------- /integration_tests/samples/openid_connect/requirements.txt: -------------------------------------------------------------------------------- 1 | slack-sdk 2 | Flask>=2,<3 3 | pyjwt>=2.1,<3 4 | cryptography>=3.4,<4 5 | Sanic>=21.3 -------------------------------------------------------------------------------- /integration_tests/samples/readme/async_script.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/readme/async_script.py 7 | 8 | import asyncio 9 | import os 10 | from slack_sdk.web.async_client import AsyncWebClient 11 | from slack_sdk.errors import SlackApiError 12 | 13 | client = AsyncWebClient(token=os.environ["SLACK_API_TOKEN"]) 14 | future = client.chat_postMessage(channel="#random", text="Hello world!") 15 | 16 | loop = asyncio.get_event_loop() 17 | try: 18 | # run_until_complete returns the Future's result, or raise its exception. 19 | response = loop.run_until_complete(future) 20 | assert response["message"]["text"] == "Hello world!" 21 | except SlackApiError as e: 22 | assert e.response["ok"] is False 23 | assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' 24 | print(f"Got an error: {e.response['error']}") 25 | finally: 26 | loop.close() 27 | -------------------------------------------------------------------------------- /integration_tests/samples/readme/proxy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/readme/proxy.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | from ssl import SSLContext 11 | 12 | sslcert = SSLContext() 13 | # pip3 install proxy.py 14 | # proxy --port 9000 --log-level d 15 | proxyinfo = "http://localhost:9000" 16 | 17 | client = WebClient(token=os.environ["SLACK_API_TOKEN"], ssl=sslcert, proxy=proxyinfo) 18 | response = client.chat_postMessage(channel="#random", text="Hello World!") 19 | print(response) 20 | -------------------------------------------------------------------------------- /integration_tests/samples/readme/rtm_client_basics.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/readme/rtm_client_basics.py 7 | 8 | import os 9 | from slack_sdk.rtm import RTMClient 10 | from slack_sdk.errors import SlackApiError 11 | 12 | 13 | @RTMClient.run_on(event="message") 14 | def say_hello(**payload): 15 | data = payload["data"] 16 | web_client = payload["web_client"] 17 | rtm_client = payload["rtm_client"] 18 | if "text" in data and "Hello" in data.get("text", []): 19 | channel_id = data["channel"] 20 | thread_ts = data["ts"] 21 | user = data["user"] 22 | 23 | try: 24 | response = web_client.chat_postMessage(channel=channel_id, text=f"Hi <@{user}>!", thread_ts=thread_ts) 25 | except SlackApiError as e: 26 | # You will get a SlackApiError if "ok" is False 27 | assert e.response["ok"] is False 28 | assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' 29 | print(f"Got an error: {e.response['error']}") 30 | 31 | 32 | rtm_client = RTMClient(token=os.environ["SLACK_API_TOKEN"]) 33 | rtm_client.start() 34 | -------------------------------------------------------------------------------- /integration_tests/samples/readme/sending_messages.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # python3 integration_tests/samples/readme/sending_messages.py 7 | 8 | import os 9 | from slack_sdk.web import WebClient 10 | from slack_sdk.errors import SlackApiError 11 | 12 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 13 | 14 | try: 15 | response = client.chat_postMessage(channel="#random", text="Hello world!") 16 | assert response["message"]["text"] == "Hello world!" 17 | except SlackApiError as e: 18 | # You will get a SlackApiError if "ok" is False 19 | assert e.response["ok"] is False 20 | assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' 21 | print(f"Got an error: {e.response['error']}") 22 | -------------------------------------------------------------------------------- /integration_tests/samples/readme/uploading_files.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | # export SLACK_API_TOKEN=xoxb-*** 6 | # echo 'Hello world!' > tmp.txt 7 | # python3 integration_tests/samples/readme/uploading_files.py 8 | 9 | import os 10 | from slack_sdk.web import WebClient 11 | from slack_sdk.errors import SlackApiError 12 | 13 | client = WebClient(token=os.environ["SLACK_API_TOKEN"]) 14 | 15 | try: 16 | filepath = "./tmp.txt" 17 | response = client.files_upload(channels="#random", file=filepath) 18 | assert response["file"] # the uploaded file 19 | except SlackApiError as e: 20 | # You will get a SlackApiError if "ok" is False 21 | assert e.response["ok"] is False 22 | assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' 23 | print(f"Got an error: {e.response['error']}") 24 | -------------------------------------------------------------------------------- /integration_tests/samples/rtm_v2/rtm_v2_app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig( 4 | level=logging.DEBUG, 5 | format="%(asctime)s.%(msecs)03d %(levelname)s %(filename)s (%(lineno)s): %(message)s", 6 | datefmt="%Y-%m-%d %H:%M:%S", 7 | ) 8 | logger = logging.getLogger(__name__) 9 | 10 | import os 11 | from slack_sdk.rtm.v2 import RTMClient 12 | from integration_tests.env_variable_names import SLACK_SDK_TEST_CLASSIC_APP_BOT_TOKEN 13 | 14 | if __name__ == "__main__": 15 | rtm = RTMClient( 16 | token=os.environ.get(SLACK_SDK_TEST_CLASSIC_APP_BOT_TOKEN), 17 | trace_enabled=True, 18 | all_message_trace_enabled=True, 19 | ) 20 | 21 | @rtm.on("message") 22 | def handle(client: RTMClient, event: dict): 23 | client.web_client.reactions_add( 24 | channel=event["channel"], 25 | timestamp=event["ts"], 26 | name="eyes", 27 | ) 28 | 29 | @rtm.on("*") 30 | def handle(client: RTMClient, event: dict): 31 | logger.info(event) 32 | 33 | rtm.start() 34 | -------------------------------------------------------------------------------- /integration_tests/samples/rtm_v2/rtm_v2_proxy_app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig( 4 | level=logging.DEBUG, 5 | format="%(asctime)s.%(msecs)03d %(levelname)s %(filename)s (%(lineno)s): %(message)s", 6 | datefmt="%Y-%m-%d %H:%M:%S", 7 | ) 8 | logger = logging.getLogger(__name__) 9 | 10 | import os 11 | from slack_sdk.rtm.v2 import RTMClient 12 | from integration_tests.env_variable_names import SLACK_SDK_TEST_CLASSIC_APP_BOT_TOKEN 13 | 14 | # pip3 install proxy.py 15 | # proxy --port 9000 --log-level d 16 | proxy_url = "http://localhost:9000" 17 | 18 | if __name__ == "__main__": 19 | rtm = RTMClient( 20 | token=os.environ.get(SLACK_SDK_TEST_CLASSIC_APP_BOT_TOKEN), 21 | trace_enabled=True, 22 | all_message_trace_enabled=True, 23 | proxy=proxy_url, 24 | ) 25 | 26 | @rtm.on("message") 27 | def handle(client: RTMClient, event: dict): 28 | client.web_client.reactions_add( 29 | channel=event["channel"], 30 | timestamp=event["ts"], 31 | name="eyes", 32 | ) 33 | 34 | @rtm.on("*") 35 | def handle(client: RTMClient, event: dict): 36 | logger.info(event) 37 | 38 | rtm.start() 39 | -------------------------------------------------------------------------------- /integration_tests/samples/scim/search_groups.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from slack_sdk.scim import SCIMClient 7 | 8 | client = SCIMClient(token=os.environ["SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN"]) 9 | 10 | response = client.search_groups(start_index=1, count=2) 11 | print("-----------------------") 12 | print(response.groups) 13 | -------------------------------------------------------------------------------- /integration_tests/samples/scim/search_groups_async.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import asyncio 6 | import os 7 | from slack_sdk.scim.async_client import AsyncSCIMClient 8 | 9 | client = AsyncSCIMClient(token=os.environ["SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN"]) 10 | 11 | 12 | async def main(): 13 | response = await client.search_groups(start_index=1, count=2) 14 | print("-----------------------") 15 | print(response.groups) 16 | 17 | 18 | asyncio.run(main()) 19 | -------------------------------------------------------------------------------- /integration_tests/samples/scim/search_users.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from slack_sdk.scim import SCIMClient 7 | 8 | client = SCIMClient(token=os.environ["SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN"]) 9 | 10 | response = client.search_users(start_index=1, count=2) 11 | print("-----------------------") 12 | print(response.users) 13 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/integration_tests/samples/socket_mode/__init__.py -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/aiohttp_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import asyncio 6 | import os 7 | from slack_sdk.web.async_client import AsyncWebClient 8 | from slack_sdk.socket_mode.response import SocketModeResponse 9 | from slack_sdk.socket_mode.request import SocketModeRequest 10 | from slack_sdk.socket_mode.aiohttp import SocketModeClient 11 | 12 | 13 | async def main(): 14 | client = SocketModeClient( 15 | app_token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN"), 16 | web_client=AsyncWebClient(token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN")), 17 | trace_enabled=True, 18 | ) 19 | 20 | async def process(client: SocketModeClient, req: SocketModeRequest): 21 | if req.type == "events_api": 22 | response = SocketModeResponse(envelope_id=req.envelope_id) 23 | await client.send_socket_mode_response(response) 24 | if req.payload["event"]["type"] == "message": 25 | await client.web_client.reactions_add( 26 | name="eyes", 27 | channel=req.payload["event"]["channel"], 28 | timestamp=req.payload["event"]["ts"], 29 | ) 30 | 31 | client.socket_mode_request_listeners.append(process) 32 | await client.connect() 33 | await asyncio.sleep(float("inf")) 34 | 35 | 36 | asyncio.run(main()) 37 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_adapter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/integration_tests/samples/socket_mode/bolt_adapter/__init__.py -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_adapter/async_base_handler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from typing import Union 4 | 5 | from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient 6 | from slack_sdk.socket_mode.request import SocketModeRequest 7 | 8 | from slack_bolt import App 9 | from slack_bolt.app.async_app import AsyncApp 10 | 11 | 12 | class AsyncBaseSocketModeHandler: 13 | app: Union[App, AsyncApp] # type: ignore 14 | client: AsyncBaseSocketModeClient 15 | 16 | async def handle(self, client: AsyncBaseSocketModeClient, req: SocketModeRequest) -> None: 17 | raise NotImplementedError() 18 | 19 | async def connect_async(self): 20 | await self.client.connect() 21 | 22 | async def disconnect_async(self): 23 | await self.client.disconnect() 24 | 25 | async def close_async(self): 26 | await self.client.close() 27 | 28 | async def start_async(self): 29 | await self.connect_async() 30 | if self.app.logger.level > logging.INFO: 31 | print("⚡️ Bolt app is running!") 32 | else: 33 | self.app.logger.info("⚡️ Bolt app is running!") 34 | await asyncio.sleep(float("inf")) 35 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_adapter/base_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from threading import Event 3 | 4 | from slack_sdk.socket_mode.client import BaseSocketModeClient 5 | from slack_sdk.socket_mode.request import SocketModeRequest 6 | 7 | from slack_bolt import App 8 | 9 | 10 | class BaseSocketModeHandler: 11 | app: App # type: ignore 12 | client: BaseSocketModeClient 13 | 14 | def handle(self, client: BaseSocketModeClient, req: SocketModeRequest) -> None: 15 | raise NotImplementedError() 16 | 17 | def connect(self): 18 | self.client.connect() 19 | 20 | def disconnect(self): 21 | self.client.disconnect() 22 | 23 | def close(self): 24 | self.client.close() 25 | 26 | def start(self): 27 | self.connect() 28 | if self.app.logger.level > logging.INFO: 29 | print("⚡️ Bolt app is running!") 30 | else: 31 | self.app.logger.info("⚡️ Bolt app is running!") 32 | Event().wait() 33 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_adapter/builtin.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import time 3 | from typing import Optional 4 | 5 | from slack_sdk.socket_mode.request import SocketModeRequest 6 | from slack_sdk.socket_mode.builtin import SocketModeClient 7 | 8 | from slack_bolt import App 9 | from .base_handler import BaseSocketModeHandler 10 | from .internals import run_bolt_app, send_response 11 | from slack_bolt.response import BoltResponse 12 | 13 | 14 | class SocketModeHandler(BaseSocketModeHandler): 15 | app: App # type: ignore 16 | app_token: str 17 | client: SocketModeClient 18 | 19 | def __init__( # type: ignore 20 | self, 21 | app: App, # type: ignore 22 | app_token: Optional[str] = None, 23 | ): 24 | self.app = app 25 | self.app_token = app_token or os.environ["SLACK_APP_TOKEN"] 26 | self.client = SocketModeClient(app_token=self.app_token) 27 | self.client.socket_mode_request_listeners.append(self.handle) 28 | 29 | def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None: 30 | start = time() 31 | bolt_resp: BoltResponse = run_bolt_app(self.app, req) 32 | send_response(client, req, bolt_resp, start) 33 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_adapter/websocket_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import time 3 | from typing import Optional 4 | 5 | from slack_sdk.socket_mode.request import SocketModeRequest 6 | from slack_sdk.socket_mode.websocket_client import SocketModeClient 7 | 8 | from slack_bolt import App 9 | from .base_handler import BaseSocketModeHandler 10 | from .internals import run_bolt_app, send_response 11 | from slack_bolt.response import BoltResponse 12 | 13 | 14 | class SocketModeHandler(BaseSocketModeHandler): 15 | app: App # type: ignore 16 | app_token: str 17 | client: SocketModeClient 18 | 19 | def __init__( # type: ignore 20 | self, 21 | app: App, # type: ignore 22 | app_token: Optional[str] = None, 23 | ): 24 | self.app = app 25 | self.app_token = app_token or os.environ["SLACK_APP_TOKEN"] 26 | self.client = SocketModeClient(app_token=self.app_token) 27 | self.client.socket_mode_request_listeners.append(self.handle) 28 | 29 | def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None: 30 | start = time() 31 | bolt_resp: BoltResponse = run_bolt_app(self.app, req) 32 | send_response(client, req, bolt_resp, start) 33 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_aiohttp_async_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from slack_bolt.app.async_app import AsyncApp 7 | from slack_bolt.context.async_context import AsyncBoltContext 8 | 9 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 10 | app = AsyncApp(signing_secret="will-be-removed-soon", token=bot_token) 11 | 12 | 13 | @app.event("app_mention") 14 | async def mention(context: AsyncBoltContext): 15 | await context.say(":wave: Hi there!") 16 | 17 | 18 | @app.event("message") 19 | async def message(context: AsyncBoltContext, event: dict): 20 | await context.client.reactions_add( 21 | channel=event["channel"], 22 | timestamp=event["ts"], 23 | name="eyes", 24 | ) 25 | 26 | 27 | @app.command("/hello-socket-mode") 28 | async def hello_command(ack, body): 29 | user_id = body["user_id"] 30 | await ack(f"Hi <@{user_id}>!") 31 | 32 | 33 | async def main(): 34 | from bolt_adapter.aiohttp import AsyncSocketModeHandler 35 | 36 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 37 | await AsyncSocketModeHandler(app, app_token).start_async() 38 | 39 | 40 | if __name__ == "__main__": 41 | import asyncio 42 | 43 | asyncio.run(main()) 44 | 45 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 46 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 47 | # pip install .[optional] 48 | # pip install slack_bolt 49 | # python integration_tests/samples/socket_mode/{this file name}.py 50 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_aiohttp_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | 7 | from slack_bolt.app import App 8 | from slack_bolt.context import BoltContext 9 | 10 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 11 | app = App(signing_secret="will-be-removed-soon", token=bot_token) 12 | 13 | 14 | @app.event("app_mention") 15 | def mention(context: BoltContext): 16 | context.say(":wave: Hi there!") 17 | 18 | 19 | @app.event("message") 20 | def message(context: BoltContext, event: dict): 21 | context.client.reactions_add( 22 | channel=event["channel"], 23 | timestamp=event["ts"], 24 | name="eyes", 25 | ) 26 | 27 | 28 | @app.command("/hello-socket-mode") 29 | def hello_command(ack, body): 30 | user_id = body["user_id"] 31 | ack(f"Hi <@{user_id}>!") 32 | 33 | 34 | async def main(): 35 | from bolt_adapter.aiohttp import SocketModeHandler 36 | 37 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 38 | await SocketModeHandler(app, app_token).start_async() 39 | 40 | 41 | if __name__ == "__main__": 42 | import asyncio 43 | 44 | asyncio.run(main()) 45 | 46 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 47 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 48 | # pip install .[optional] 49 | # pip install slack_bolt 50 | # python integration_tests/samples/socket_mode/{this file name}.py 51 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_builtin_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | 7 | from slack_bolt.app import App 8 | from slack_bolt.context import BoltContext 9 | 10 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 11 | app = App(signing_secret="will-be-removed-soon", token=bot_token) 12 | 13 | 14 | @app.event("app_mention") 15 | def mention(context: BoltContext): 16 | context.say(":wave: Hi there!") 17 | 18 | 19 | @app.event("message") 20 | def message(context: BoltContext, event: dict): 21 | context.client.reactions_add( 22 | channel=event["channel"], 23 | timestamp=event["ts"], 24 | name="eyes", 25 | ) 26 | 27 | 28 | @app.command("/hello-socket-mode") 29 | def hello_command(ack, body): 30 | user_id = body["user_id"] 31 | ack(f"Hi <@{user_id}>!") 32 | 33 | 34 | if __name__ == "__main__": 35 | from bolt_adapter.builtin import SocketModeHandler 36 | 37 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 38 | SocketModeHandler(app, app_token).start() 39 | 40 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 41 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 42 | # pip install .[optional] 43 | # pip install slack_bolt 44 | # python integration_tests/samples/socket_mode/{this file name}.py 45 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_websocket_client_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | 7 | from slack_bolt.app import App 8 | from slack_bolt.context import BoltContext 9 | 10 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 11 | app = App(signing_secret="will-be-removed-soon", token=bot_token) 12 | 13 | 14 | @app.event("app_mention") 15 | def mention(context: BoltContext): 16 | context.say(":wave: Hi there!") 17 | 18 | 19 | @app.event("message") 20 | def message(context: BoltContext, event: dict): 21 | context.client.reactions_add( 22 | channel=event["channel"], 23 | timestamp=event["ts"], 24 | name="eyes", 25 | ) 26 | 27 | 28 | @app.command("/hello-socket-mode") 29 | def hello_command(ack, body): 30 | user_id = body["user_id"] 31 | ack(f"Hi <@{user_id}>!") 32 | 33 | 34 | if __name__ == "__main__": 35 | from bolt_adapter.websocket_client import SocketModeHandler 36 | 37 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 38 | SocketModeHandler(app, app_token).start() 39 | 40 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 41 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 42 | # pip install .[optional] 43 | # pip install slack_bolt 44 | # python integration_tests/samples/socket_mode/{this file name}.py 45 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_websockets_async_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from slack_bolt.app.async_app import AsyncApp 7 | from slack_bolt.context.async_context import AsyncBoltContext 8 | 9 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 10 | app = AsyncApp(signing_secret="will-be-removed-soon", token=bot_token) 11 | 12 | 13 | @app.event("app_mention") 14 | async def mention(context: AsyncBoltContext): 15 | await context.say(":wave: Hi there!") 16 | 17 | 18 | @app.event("message") 19 | async def message(context: AsyncBoltContext, event: dict): 20 | await context.client.reactions_add( 21 | channel=event["channel"], 22 | timestamp=event["ts"], 23 | name="eyes", 24 | ) 25 | 26 | 27 | @app.command("/hello-socket-mode") 28 | async def hello_command(ack, body): 29 | user_id = body["user_id"] 30 | await ack(f"Hi <@{user_id}>!") 31 | 32 | 33 | async def main(): 34 | from bolt_adapter.websockets import AsyncSocketModeHandler 35 | 36 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 37 | await AsyncSocketModeHandler(app, app_token).start_async() 38 | 39 | 40 | if __name__ == "__main__": 41 | import asyncio 42 | 43 | asyncio.run(main()) 44 | 45 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 46 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 47 | # pip install .[optional] 48 | # pip install slack_bolt 49 | # python integration_tests/samples/socket_mode/{this file name}.py 50 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/bolt_websockets_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | 7 | from slack_bolt.app import App 8 | from slack_bolt.context import BoltContext 9 | 10 | bot_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN") 11 | app = App(signing_secret="will-be-removed-soon", token=bot_token) 12 | 13 | 14 | @app.event("app_mention") 15 | def mention(context: BoltContext): 16 | context.say(":wave: Hi there!") 17 | 18 | 19 | @app.event("message") 20 | def message(context: BoltContext, event: dict): 21 | context.client.reactions_add( 22 | channel=event["channel"], 23 | timestamp=event["ts"], 24 | name="eyes", 25 | ) 26 | 27 | 28 | @app.command("/hello-socket-mode") 29 | def hello_command(ack, body): 30 | user_id = body["user_id"] 31 | ack(f"Hi <@{user_id}>!") 32 | 33 | 34 | async def main(): 35 | from bolt_adapter.websockets import SocketModeHandler 36 | 37 | app_token = os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN") 38 | await SocketModeHandler(app, app_token).start_async() 39 | 40 | 41 | if __name__ == "__main__": 42 | import asyncio 43 | 44 | asyncio.run(main()) 45 | 46 | # export SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN= 47 | # export SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN= 48 | # pip install .[optional] 49 | # pip install slack_bolt 50 | # python integration_tests/samples/socket_mode/{this file name}.py 51 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/builtin_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig( 4 | level=logging.DEBUG, 5 | format="%(asctime)s.%(msecs)03d %(levelname)s %(pathname)s (%(lineno)s): %(message)s", 6 | datefmt="%Y-%m-%d %H:%M:%S", 7 | ) 8 | 9 | import os 10 | from threading import Event 11 | from slack_sdk.web import WebClient 12 | from slack_sdk.socket_mode.response import SocketModeResponse 13 | from slack_sdk.socket_mode.request import SocketModeRequest 14 | from slack_sdk.socket_mode import SocketModeClient 15 | 16 | client = SocketModeClient( 17 | app_token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN"), 18 | web_client=WebClient(token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN")), 19 | trace_enabled=True, 20 | all_message_trace_enabled=True, 21 | ) 22 | 23 | if __name__ == "__main__": 24 | 25 | def process(client: SocketModeClient, req: SocketModeRequest): 26 | if req.type == "events_api": 27 | response = SocketModeResponse(envelope_id=req.envelope_id) 28 | client.send_socket_mode_response(response) 29 | client.web_client.reactions_add( 30 | name="eyes", 31 | channel=req.payload["event"]["channel"], 32 | timestamp=req.payload["event"]["ts"], 33 | ) 34 | 35 | client.socket_mode_request_listeners.append(process) 36 | client.connect() 37 | Event().wait() 38 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/websocket_client_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from threading import Event 7 | from slack_sdk.web import WebClient 8 | from slack_sdk.socket_mode.response import SocketModeResponse 9 | from slack_sdk.socket_mode.request import SocketModeRequest 10 | from slack_sdk.socket_mode.websocket_client import SocketModeClient 11 | 12 | client = SocketModeClient( 13 | app_token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN"), 14 | web_client=WebClient(token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN")), 15 | trace_enabled=True, 16 | ) 17 | 18 | if __name__ == "__main__": 19 | 20 | def process(client: SocketModeClient, req: SocketModeRequest): 21 | if req.type == "events_api": 22 | response = SocketModeResponse(envelope_id=req.envelope_id) 23 | client.send_socket_mode_response(response) 24 | client.web_client.reactions_add( 25 | name="eyes", 26 | channel=req.payload["event"]["channel"], 27 | timestamp=req.payload["event"]["ts"], 28 | ) 29 | 30 | client.socket_mode_request_listeners.append(process) 31 | client.connect() 32 | Event().wait() 33 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/websocket_client_proxy_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import os 6 | from threading import Event 7 | from slack_sdk.web import WebClient 8 | from slack_sdk.socket_mode.response import SocketModeResponse 9 | from slack_sdk.socket_mode.request import SocketModeRequest 10 | from slack_sdk.socket_mode.websocket_client import SocketModeClient 11 | 12 | client = SocketModeClient( 13 | app_token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN"), 14 | web_client=WebClient( 15 | token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN"), 16 | # pip3 install proxy.py 17 | # proxy --port 9000 --log-level d 18 | proxy="http://localhost:9000", 19 | ), 20 | trace_enabled=True, 21 | # pip3 install proxy.py 22 | # proxy --port 9000 --log-level d 23 | http_proxy_host="localhost", 24 | http_proxy_port=9000, 25 | http_proxy_auth=("user", "pass"), 26 | ) 27 | 28 | if __name__ == "__main__": 29 | 30 | def process(client: SocketModeClient, req: SocketModeRequest): 31 | if req.type == "events_api": 32 | response = SocketModeResponse(envelope_id=req.envelope_id) 33 | client.send_socket_mode_response(response) 34 | client.web_client.reactions_add( 35 | name="eyes", 36 | channel=req.payload["event"]["channel"], 37 | timestamp=req.payload["event"]["ts"], 38 | ) 39 | 40 | client.socket_mode_request_listeners.append(process) 41 | client.connect() 42 | Event().wait() 43 | -------------------------------------------------------------------------------- /integration_tests/samples/socket_mode/websockets_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.DEBUG) 4 | 5 | import asyncio 6 | import os 7 | from slack_sdk.web.async_client import AsyncWebClient 8 | from slack_sdk.socket_mode.response import SocketModeResponse 9 | from slack_sdk.socket_mode.request import SocketModeRequest 10 | from slack_sdk.socket_mode.websockets import SocketModeClient 11 | 12 | 13 | async def main(): 14 | client = SocketModeClient( 15 | app_token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN"), 16 | web_client=AsyncWebClient(token=os.environ.get("SLACK_SDK_TEST_SOCKET_MODE_BOT_TOKEN")), 17 | ) 18 | 19 | async def process(client: SocketModeClient, req: SocketModeRequest): 20 | if req.type == "events_api": 21 | response = SocketModeResponse(envelope_id=req.envelope_id) 22 | await client.send_socket_mode_response(response) 23 | 24 | await client.web_client.reactions_add( 25 | name="eyes", 26 | channel=req.payload["event"]["channel"], 27 | timestamp=req.payload["event"]["ts"], 28 | ) 29 | 30 | client.socket_mode_request_listeners.append(process) 31 | await client.connect() 32 | await asyncio.sleep(float("inf")) 33 | 34 | 35 | asyncio.run(main()) 36 | -------------------------------------------------------------------------------- /integration_tests/samples/token_rotation/.gitignore: -------------------------------------------------------------------------------- 1 | .env* -------------------------------------------------------------------------------- /integration_tests/web/test_admin_roles.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | import time 5 | import unittest 6 | 7 | from integration_tests.env_variable_names import ( 8 | SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN, 9 | SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID, 10 | SLACK_SDK_TEST_GRID_TEAM_ID, 11 | SLACK_SDK_TEST_GRID_USER_ID, 12 | ) 13 | from integration_tests.helpers import async_test 14 | from slack_sdk.web import WebClient 15 | from slack_sdk.web.async_client import AsyncWebClient 16 | 17 | 18 | class TestWebClient(unittest.TestCase): 19 | """Runs integration tests with real Slack API""" 20 | 21 | def setUp(self): 22 | self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def test_sync(self): 28 | client: WebClient = WebClient(token=self.org_admin_token) 29 | list_response = client.admin_roles_listAssignments(role_ids=["Rl0A"], limit=3, sort_dir="DESC") 30 | self.assertGreater(len(list_response.get("role_assignments", [])), 0) 31 | # TODO tests for add/remove 32 | 33 | @async_test 34 | async def test_async(self): 35 | client: AsyncWebClient = AsyncWebClient(token=self.org_admin_token) 36 | list_response = await client.admin_roles_listAssignments(role_ids=["Rl0A"], limit=3, sort_dir="DESC") 37 | self.assertGreater(len(list_response.get("role_assignments", [])), 0) 38 | # TODO tests for add/remove 39 | -------------------------------------------------------------------------------- /integration_tests/web/test_admin_users_unsupportedVersions_export.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import time 4 | 5 | from integration_tests.env_variable_names import ( 6 | SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN, 7 | ) 8 | from slack_sdk.web import WebClient 9 | 10 | 11 | class TestWebClient(unittest.TestCase): 12 | def setUp(self): 13 | self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] 14 | self.client: WebClient = WebClient(token=self.org_admin_token) 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def test_no_args(self): 20 | response = self.client.admin_users_unsupportedVersions_export() 21 | self.assertIsNone(response.get("error")) 22 | 23 | def test_full_args(self): 24 | response = self.client.admin_users_unsupportedVersions_export( 25 | date_end_of_support=int(round(time.time())) + 60 * 60 * 24 * 120, 26 | date_sessions_started=0, 27 | ) 28 | self.assertIsNone(response.get("error")) 29 | 30 | def test_full_args_str(self): 31 | response = self.client.admin_users_unsupportedVersions_export( 32 | date_end_of_support=str(int(round(time.time())) + 60 * 60 * 24 * 120), 33 | date_sessions_started="0", 34 | ) 35 | self.assertIsNone(response.get("error")) 36 | -------------------------------------------------------------------------------- /integration_tests/web/test_issue_1143.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from integration_tests.env_variable_names import SLACK_SDK_TEST_BOT_TOKEN 5 | from integration_tests.helpers import async_test 6 | from slack_sdk.errors import SlackApiError 7 | from slack_sdk.web import WebClient 8 | from slack_sdk.web.async_client import AsyncWebClient 9 | 10 | 11 | class TestWebClient(unittest.TestCase): 12 | """Runs integration tests with real Slack API 13 | 14 | export SLACK_SDK_TEST_BOT_TOKEN=xoxb-xxx 15 | ./scripts/run_integration_tests.sh integration_tests/web/test_issue_1143.py 16 | 17 | https://github.com/slackapi/python-slack-sdk/issues/1143 18 | """ 19 | 20 | def setUp(self): 21 | self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] 22 | 23 | def tearDown(self): 24 | pass 25 | 26 | def test_backward_compatible_header(self): 27 | client: WebClient = WebClient(token=self.bot_token) 28 | try: 29 | while True: 30 | client.users_list() 31 | except SlackApiError as e: 32 | self.assertIsNotNone(e.response.headers["Retry-After"]) 33 | 34 | @async_test 35 | async def test_backward_compatible_header_async(self): 36 | client: AsyncWebClient = AsyncWebClient(token=self.bot_token) 37 | try: 38 | while True: 39 | await client.users_list() 40 | except SlackApiError as e: 41 | self.assertIsNotNone(e.response.headers["Retry-After"]) 42 | -------------------------------------------------------------------------------- /integration_tests/web/test_issue_378.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | import unittest 5 | 6 | from integration_tests.env_variable_names import SLACK_SDK_TEST_USER_TOKEN 7 | from integration_tests.helpers import async_test 8 | from slack_sdk.web import WebClient 9 | from slack_sdk.web.async_client import AsyncWebClient 10 | 11 | 12 | class TestWebClient(unittest.TestCase): 13 | """Runs integration tests with real Slack API 14 | 15 | https://github.com/slackapi/python-slack-sdk/issues/378 16 | """ 17 | 18 | def setUp(self): 19 | self.logger = logging.getLogger(__name__) 20 | self.user_token = os.environ[SLACK_SDK_TEST_USER_TOKEN] 21 | self.sync_client: WebClient = WebClient(token=self.user_token) 22 | self.async_client: AsyncWebClient = AsyncWebClient(token=self.user_token) 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def test_issue_378(self): 28 | client = self.sync_client 29 | response = client.users_setPhoto(image="tests/data/slack_logo_new.png") 30 | self.assertIsNotNone(response) 31 | 32 | @async_test 33 | async def test_issue_378_async(self): 34 | client = self.async_client 35 | response = await client.users_setPhoto(image="tests/data/slack_logo_new.png") 36 | self.assertIsNotNone(response) 37 | -------------------------------------------------------------------------------- /integration_tests/web/test_issue_714.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import unittest 4 | from urllib.error import URLError 5 | 6 | from aiohttp import ClientConnectorError 7 | 8 | from integration_tests.env_variable_names import SLACK_SDK_TEST_BOT_TOKEN 9 | from integration_tests.helpers import async_test 10 | from slack_sdk.web import WebClient 11 | from slack_sdk.web.async_client import AsyncWebClient 12 | 13 | 14 | class TestWebClient(unittest.TestCase): 15 | def setUp(self): 16 | self.proxy = "http://invalid-host:9999" 17 | self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] 18 | 19 | def tearDown(self): 20 | pass 21 | 22 | def test_proxy_failure(self): 23 | client: WebClient = WebClient(token=self.bot_token, proxy=self.proxy) 24 | with self.assertRaises(URLError): 25 | client.auth_test() 26 | 27 | @async_test 28 | async def test_proxy_failure_async(self): 29 | client: AsyncWebClient = AsyncWebClient(token=self.bot_token, proxy=self.proxy) 30 | with self.assertRaises(ClientConnectorError): 31 | await client.auth_test() 32 | -------------------------------------------------------------------------------- /integration_tests/web/test_issue_809.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import io 3 | import logging 4 | import os 5 | import unittest 6 | 7 | from integration_tests.env_variable_names import SLACK_SDK_TEST_BOT_TOKEN 8 | from integration_tests.helpers import async_test 9 | from slack_sdk.web import WebClient 10 | from slack_sdk.web.async_client import AsyncWebClient 11 | 12 | 13 | class TestWebClient(unittest.TestCase): 14 | """Runs integration tests with real Slack API 15 | 16 | https://github.com/slackapi/python-slack-sdk/issues/809 17 | """ 18 | 19 | def setUp(self): 20 | self.logger = logging.getLogger(__name__) 21 | self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] 22 | self.sync_client: WebClient = WebClient(token=self.bot_token) 23 | self.async_client: AsyncWebClient = AsyncWebClient(token=self.bot_token) 24 | 25 | def tearDown(self): 26 | pass 27 | 28 | def test_issue_809(self): 29 | client = self.sync_client 30 | buff = io.BytesIO(b"here is my data but not sure what is wrong.......") 31 | buff.seek(0) 32 | upload = client.files_upload(file=buff) 33 | self.assertIsNotNone(upload) 34 | 35 | @async_test 36 | async def test_issue_809_async(self): 37 | client = self.async_client 38 | buff = io.BytesIO(b"here is my data but not sure what is wrong.......") 39 | buff.seek(0) 40 | upload = await client.files_upload(file=buff) 41 | self.assertIsNotNone(upload) 42 | -------------------------------------------------------------------------------- /integration_tests/web/test_team.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from integration_tests.env_variable_names import SLACK_SDK_TEST_BOT_TOKEN 5 | from slack_sdk.web import WebClient 6 | 7 | 8 | class TestWebClient(unittest.TestCase): 9 | def setUp(self): 10 | self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] 11 | self.client: WebClient = WebClient(token=self.bot_token) 12 | 13 | def tearDown(self): 14 | pass 15 | 16 | def test_team_billing_info(self): 17 | response = self.client.team_billing_info() 18 | self.assertIsNone(response.get("error")) 19 | self.assertIsNotNone(response.get("plan")) 20 | 21 | def test_team_preferences_list(self): 22 | response = self.client.team_preferences_list() 23 | self.assertIsNone(response.get("error")) 24 | self.assertIsNotNone(response.get("msg_edit_window_mins")) 25 | self.assertIsNotNone(response.get("allow_message_deletion")) 26 | self.assertIsNotNone(response.get("display_real_names")) 27 | self.assertIsNotNone(response.get("disable_file_uploads")) 28 | self.assertIsNotNone(response.get("who_can_post_general")) 29 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/logs/.gitkeep -------------------------------------------------------------------------------- /requirements/documentation.txt: -------------------------------------------------------------------------------- 1 | docutils==0.21.2 2 | pdoc3==0.11.6 3 | -------------------------------------------------------------------------------- /requirements/optional.txt: -------------------------------------------------------------------------------- 1 | # pip install -r requirements/optional.txt 2 | # async modules depend on aiohttp 3 | aiodns>1.0 4 | # We recommend using 3.7.1+ for RTMClient 5 | # https://github.com/slackapi/python-slack-sdk/issues/912 6 | aiohttp>=3.7.3,<4 7 | # used only under slack_sdk/*_store 8 | boto3<=2 9 | # InstallationStore/OAuthStateStore 10 | # Since v3.20, we no longer support SQLAlchemy 1.3 or older. 11 | # If you need to use a legacy version, please add our v3.19.5 code to your project. 12 | SQLAlchemy>=1.4,<3 13 | # Socket Mode 14 | # websockets 9 is not compatible with Python 3.10 15 | websockets>=9.1,<16 16 | websocket-client>=1,<2 17 | -------------------------------------------------------------------------------- /requirements/testing.txt: -------------------------------------------------------------------------------- 1 | # pip install -r requirements/testing.txt 2 | aiohttp<4 # used for a WebSocket server mock 3 | pytest>=7.0.1,<9 4 | pytest-asyncio<1 # for async 5 | pytest-cov>=2,<7 6 | # while flake8 5.x have issues with Python 3.12, flake8 6.x requires Python >= 3.8.1, 7 | # so 5.x should be kept in order to stay compatible with Python 3.6/3.7 8 | flake8>=5.0.4,<8 9 | # Don't change this version without running CI builds; 10 | # The latest version may not be available for older Python runtime 11 | black>=22.8.0; python_version=="3.6" 12 | black==22.10.0; python_version>"3.6" 13 | click==8.0.4 # black is affected by https://github.com/pallets/click/issues/2225 14 | psutil>=6.0.0,<8 15 | # used only under slack_sdk/*_store 16 | boto3<=2 17 | # For AWS tests 18 | moto>=4.0.13,<6 19 | mypy<=1.15.0 20 | # For AsyncSQLAlchemy tests 21 | greenlet<=4 22 | aiosqlite<=1 -------------------------------------------------------------------------------- /scripts/build_pypi_package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=`dirname $0` 4 | cd ${script_dir}/.. 5 | rm -rf ./slack_sdk.egg-info 6 | 7 | pip install -U pip && \ 8 | pip install twine build && \ 9 | rm -rf dist/ build/ slack_sdk.egg-info/ && \ 10 | python -m build --sdist --wheel && \ 11 | twine check dist/* 12 | -------------------------------------------------------------------------------- /scripts/deploy_to_prod_pypi_org.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=`dirname $0` 4 | cd ${script_dir}/.. 5 | rm -rf ./slack_sdk.egg-info 6 | 7 | pip install -U pip && \ 8 | pip install twine build && \ 9 | rm -rf dist/ build/ slack_sdk.egg-info/ && \ 10 | python -m build --sdist --wheel && \ 11 | twine check dist/* && \ 12 | twine upload dist/* 13 | -------------------------------------------------------------------------------- /scripts/deploy_to_test_pypi_org.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=`dirname $0` 4 | cd ${script_dir}/.. 5 | rm -rf ./slack_sdk.egg-info 6 | 7 | pip install -U pip && \ 8 | pip install twine build && \ 9 | rm -rf dist/ build/ slack_sdk.egg-info/ && \ 10 | python -m build --sdist --wheel && \ 11 | twine check dist/* && \ 12 | twine upload --repository testpypi dist/* 13 | -------------------------------------------------------------------------------- /scripts/generate_api_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Generate API documents from the latest source code 3 | 4 | script_dir=`dirname $0` 5 | cd ${script_dir}/.. 6 | 7 | pip install -U -r requirements/documentation.txt 8 | pip install -U -r requirements/optional.txt 9 | rm -rf docs/static/api-docs 10 | pdoc slack_sdk --html -o docs/static/api-docs 11 | open docs/static/api-docs/slack_sdk/index.html 12 | -------------------------------------------------------------------------------- /scripts/run_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run all the tests or a single test 3 | # all: ./scripts/run_integration_tests.sh 4 | # single: ./scripts/run_integration_tests.sh integration_tests/web/test_async_web_client.py 5 | 6 | set -e 7 | 8 | script_dir=`dirname $0` 9 | cd ${script_dir}/.. 10 | 11 | pip install -U pip 12 | pip install -r requirements/testing.txt \ 13 | -r requirements/optional.txt 14 | 15 | echo "Generating code ..." && python scripts/codegen.py --path . 16 | echo "Running black (code formatter) ..." && black slack_sdk/ 17 | 18 | test_target="${1:-tests/integration_tests/}" 19 | PYTHONPATH=$PWD:$PYTHONPATH pytest $test_target 20 | -------------------------------------------------------------------------------- /scripts/run_mypy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ./scripts/run_mypy.sh 3 | 4 | set -e 5 | 6 | script_dir=$(dirname $0) 7 | cd ${script_dir}/.. 8 | 9 | pip install -U pip setuptools wheel 10 | pip install -r requirements/testing.txt \ 11 | -r requirements/optional.txt 12 | 13 | mypy --config-file pyproject.toml 14 | -------------------------------------------------------------------------------- /scripts/run_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run all the tests or a single test 3 | # all: ./scripts/run_unit_tests.sh 4 | # single: ./scripts/run_unit_tests.sh tests/slack_sdk_async/web/test_web_client_coverage.py 5 | 6 | set -e 7 | 8 | script_dir=`dirname $0` 9 | cd ${script_dir}/.. 10 | 11 | pip install -U pip 12 | pip install -r requirements/testing.txt \ 13 | -r requirements/optional.txt 14 | 15 | echo "Generating code ..." && python scripts/codegen.py --path . 16 | echo "Running black (code formatter) ..." && black slack_sdk/ 17 | 18 | test_target="${1:-tests/}" 19 | PYTHONPATH=$PWD:$PYTHONPATH pytest $test_target 20 | -------------------------------------------------------------------------------- /scripts/run_validation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # all: ./scripts/run_validation.sh 3 | # single: ./scripts/run_validation.sh tests/slack_sdk_async/web/test_web_client_coverage.py 4 | 5 | set -e 6 | 7 | script_dir=`dirname $0` 8 | cd ${script_dir}/.. 9 | 10 | pip install -U pip setuptools wheel 11 | pip install -r requirements/testing.txt \ 12 | -r requirements/optional.txt 13 | 14 | echo "Generating code ..." && python scripts/codegen.py --path . 15 | echo "Running black (code formatter) ..." && black slack_sdk/ 16 | 17 | black --check slack/ slack_sdk/ tests/ integration_tests/ 18 | flake8 slack/ slack_sdk/ 19 | 20 | test_target="${1:-tests/}" 21 | PYTHONPATH=$PWD:$PYTHONPATH pytest --cov-report=xml --cov=slack_sdk/ $test_target 22 | -------------------------------------------------------------------------------- /scripts/uninstall_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip uninstall -y slack-sdk && \ 4 | pip freeze | grep -v "^-e" | sed 's/@.*//' | sed 's/\=\=.*//' | xargs pip uninstall -y 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | ; Legacy package configuration, prefer pyproject.toml over setup.cfg or setup.py 2 | [metadata] 3 | url=https://github.com/slackapi/python-slack-sdk 4 | author=Slack Technologies, LLC 5 | author_email=opensource@slack.com 6 | -------------------------------------------------------------------------------- /slack/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import NullHandler 3 | from slack import deprecation 4 | 5 | deprecation.show_message(__name__, "slack_sdk.web/webhook/rtm") 6 | 7 | from slack_sdk.rtm import RTMClient # noqa 8 | from slack_sdk.web.async_client import AsyncWebClient # noqa 9 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 10 | from slack_sdk.webhook.async_client import AsyncWebhookClient # noqa 11 | from slack_sdk.webhook.client import WebhookClient # noqa 12 | 13 | # Set default logging handler to avoid "No handler found" warnings. 14 | logging.getLogger(__name__).addHandler(NullHandler()) 15 | -------------------------------------------------------------------------------- /slack/deprecation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | 5 | def show_message(old: str, new: str) -> None: 6 | skip_deprecation = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION") # for unit tests etc. 7 | if skip_deprecation: 8 | return 9 | 10 | message = ( 11 | f"{old} package is deprecated. Please use {new} package instead. " 12 | "For more info, go to https://slack.dev/python-slack-sdk/v3-migration/" 13 | ) 14 | warnings.warn(message) 15 | -------------------------------------------------------------------------------- /slack/errors.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.errors import BotUserAccessError # noqa 2 | from slack_sdk.errors import SlackApiError # noqa 3 | from slack_sdk.errors import SlackClientError # noqa 4 | from slack_sdk.errors import SlackClientNotConnectedError # noqa 5 | from slack_sdk.errors import SlackObjectFormationError # noqa 6 | from slack_sdk.errors import SlackRequestError # noqa 7 | 8 | from slack import deprecation 9 | 10 | deprecation.show_message(__name__, "slack_sdk.errors") 11 | -------------------------------------------------------------------------------- /slack/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/slack/py.typed -------------------------------------------------------------------------------- /slack/rtm/__init__.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.rtm import RTMClient # noqa 2 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 3 | 4 | from slack import deprecation 5 | 6 | deprecation.show_message(__name__, "slack_sdk.web/rtm") 7 | -------------------------------------------------------------------------------- /slack/rtm/client.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.rtm import RTMClient # noqa 2 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 3 | 4 | from slack import deprecation 5 | 6 | deprecation.show_message(__name__, "slack_sdk.rtm.client") 7 | -------------------------------------------------------------------------------- /slack/signature/__init__.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.signature import SignatureVerifier # noqa 2 | 3 | from slack import deprecation 4 | 5 | deprecation.show_message(__name__, "slack_sdk.signature") 6 | -------------------------------------------------------------------------------- /slack/version.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.version import __version__ # noqa 2 | -------------------------------------------------------------------------------- /slack/web/__init__.py: -------------------------------------------------------------------------------- 1 | import slack_sdk.version as slack_version # noqa 2 | from slack import deprecation 3 | from slack_sdk.web.async_client import AsyncSlackResponse # noqa 4 | from slack_sdk.web.async_client import AsyncWebClient # noqa 5 | from slack_sdk.web.internal_utils import _to_0_or_1_if_bool # noqa 6 | from slack_sdk.web.internal_utils import convert_bool_to_0_or_1 # noqa 7 | from slack_sdk.web.internal_utils import get_user_agent # noqa 8 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 9 | from slack_sdk.web.slack_response import SlackResponse # noqa 10 | 11 | deprecation.show_message(__name__, "slack_sdk.web") 12 | -------------------------------------------------------------------------------- /slack/web/async_client.py: -------------------------------------------------------------------------------- 1 | # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2 | # 3 | # *** DO NOT EDIT THIS FILE *** 4 | # 5 | # 1) Modify slack/web/client.py 6 | # 2) Run `python scripts/codegen.py` 7 | # 3) Run `black slack_sdk/` 8 | # 9 | # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 10 | 11 | from slack import deprecation 12 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 13 | from slack_sdk.web.async_client import AsyncWebClient # noqa 14 | from slack_sdk.web.async_client import AsyncSlackResponse # noqa 15 | 16 | deprecation.show_message(__name__, "slack_sdk.web.client") 17 | -------------------------------------------------------------------------------- /slack/web/classes/__init__.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models import BaseObject # noqa 2 | from slack_sdk.models import JsonObject # noqa 3 | from slack_sdk.models import JsonValidator # noqa 4 | from slack_sdk.models import EnumValidator # noqa 5 | from slack_sdk.models import extract_json # noqa 6 | from slack_sdk.models import show_unknown_key_warning # noqa 7 | 8 | from slack import deprecation 9 | 10 | deprecation.show_message(__name__, "slack_sdk.models") 11 | -------------------------------------------------------------------------------- /slack/web/classes/actions.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.attachments import AbstractActionSelector # noqa 2 | from slack_sdk.models.attachments import Action # noqa 3 | from slack_sdk.models.attachments import ActionButton # noqa 4 | from slack_sdk.models.attachments import ActionChannelSelector # noqa 5 | from slack_sdk.models.attachments import ActionConversationSelector # noqa 6 | from slack_sdk.models.attachments import ActionExternalSelector # noqa 7 | from slack_sdk.models.attachments import ActionLinkButton # noqa 8 | from slack_sdk.models.attachments import ActionUserSelector # noqa 9 | from slack_sdk.models.dialogs import ActionStaticSelector # noqa 10 | 11 | from slack import deprecation 12 | 13 | deprecation.show_message(__name__, "slack_sdk.models.attachments/dialogs") 14 | -------------------------------------------------------------------------------- /slack/web/classes/attachments.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.attachments import Attachment # noqa 2 | from slack_sdk.models.attachments import AttachmentField # noqa 3 | from slack_sdk.models.attachments import BlockAttachment # noqa 4 | from slack_sdk.models.attachments import InteractiveAttachment # noqa 5 | from slack_sdk.models.attachments import SeededColors # noqa 6 | 7 | from slack import deprecation 8 | 9 | deprecation.show_message(__name__, "slack_sdk.models.attachments") 10 | -------------------------------------------------------------------------------- /slack/web/classes/blocks.py: -------------------------------------------------------------------------------- 1 | from slack import deprecation 2 | from slack_sdk.models.blocks import ActionsBlock # noqa 3 | from slack_sdk.models.blocks import Block # noqa 4 | from slack_sdk.models.blocks import CallBlock # noqa 5 | from slack_sdk.models.blocks import ContextBlock # noqa 6 | from slack_sdk.models.blocks import DividerBlock # noqa 7 | from slack_sdk.models.blocks import FileBlock # noqa 8 | from slack_sdk.models.blocks import HeaderBlock # noqa 9 | from slack_sdk.models.blocks import ImageBlock # noqa 10 | from slack_sdk.models.blocks import InputBlock # noqa 11 | from slack_sdk.models.blocks import SectionBlock # noqa 12 | 13 | deprecation.show_message(__name__, "slack_sdk.models.blocks") 14 | -------------------------------------------------------------------------------- /slack/web/classes/dialog_elements.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.dialogs import AbstractDialogSelector # noqa 2 | from slack_sdk.models.dialogs import DialogChannelSelector # noqa 3 | from slack_sdk.models.dialogs import DialogConversationSelector # noqa 4 | from slack_sdk.models.dialogs import DialogExternalSelector # noqa 5 | from slack_sdk.models.dialogs import DialogStaticSelector # noqa 6 | from slack_sdk.models.dialogs import DialogTextArea # noqa 7 | from slack_sdk.models.dialogs import DialogTextComponent # noqa 8 | from slack_sdk.models.dialogs import DialogTextField # noqa 9 | from slack_sdk.models.dialogs import DialogUserSelector # noqa 10 | from slack_sdk.models.dialogs import TextElementSubtypes # noqa 11 | 12 | from slack import deprecation 13 | 14 | deprecation.show_message(__name__, "slack_sdk.models.blocks") 15 | -------------------------------------------------------------------------------- /slack/web/classes/dialogs.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.dialogs import DialogBuilder # noqa 2 | 3 | from slack import deprecation 4 | 5 | deprecation.show_message(__name__, "slack_sdk.models.dialogs") 6 | -------------------------------------------------------------------------------- /slack/web/classes/messages.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.messages.message import Message # noqa 2 | -------------------------------------------------------------------------------- /slack/web/classes/objects.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.blocks import ButtonStyles # noqa 2 | from slack_sdk.models.blocks import ConfirmObject # noqa 3 | from slack_sdk.models.blocks import DynamicSelectElementTypes # noqa 4 | from slack_sdk.models.blocks import MarkdownTextObject # noqa 5 | from slack_sdk.models.blocks import Option # noqa 6 | from slack_sdk.models.blocks import OptionGroup # noqa 7 | from slack_sdk.models.blocks import PlainTextObject # noqa 8 | from slack_sdk.models.blocks import TextObject # noqa 9 | from slack_sdk.models.messages import ChannelLink # noqa 10 | from slack_sdk.models.messages import DateLink # noqa 11 | from slack_sdk.models.messages import EveryoneLink # noqa 12 | from slack_sdk.models.messages import HereLink # noqa 13 | from slack_sdk.models.messages import Link # noqa 14 | from slack_sdk.models.messages import ObjectLink # noqa 15 | 16 | 17 | from slack import deprecation 18 | 19 | deprecation.show_message(__name__, "slack_sdk.models.blocks/messages") 20 | -------------------------------------------------------------------------------- /slack/web/classes/views.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.views import View # noqa 2 | from slack_sdk.models.views import ViewState # noqa 3 | from slack_sdk.models.views import ViewStateValue # noqa 4 | 5 | from slack import deprecation 6 | 7 | deprecation.show_message(__name__, "slack_sdk.models.views") 8 | -------------------------------------------------------------------------------- /slack/web/client.py: -------------------------------------------------------------------------------- 1 | from slack import deprecation 2 | from slack_sdk.web.legacy_client import LegacyWebClient as WebClient # noqa 3 | 4 | deprecation.show_message(__name__, "slack_sdk.web.client") 5 | -------------------------------------------------------------------------------- /slack/web/deprecation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | # https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api 5 | deprecated_method_prefixes_2020_01 = [ 6 | "channels.", 7 | "groups.", 8 | "im.", 9 | "mpim.", 10 | "admin.conversations.whitelist.", 11 | ] 12 | 13 | 14 | def show_2020_01_deprecation(method_name: str): 15 | """Prints a warning if the given method is deprecated""" 16 | 17 | skip_deprecation = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION") # for unit tests etc. 18 | if skip_deprecation: 19 | return 20 | if not method_name: 21 | return 22 | 23 | matched_prefixes = [prefix for prefix in deprecated_method_prefixes_2020_01 if method_name.startswith(prefix)] 24 | if len(matched_prefixes) > 0: 25 | message = ( 26 | f"{method_name} is deprecated. Please use the Conversations API instead. " 27 | "For more info, go to " 28 | "https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api" 29 | ) 30 | warnings.warn(message) 31 | -------------------------------------------------------------------------------- /slack/web/slack_response.py: -------------------------------------------------------------------------------- 1 | from slack import deprecation 2 | from slack_sdk.web.legacy_slack_response import ( # noqa 3 | LegacySlackResponse as SlackResponse, 4 | ) 5 | 6 | deprecation.show_message(__name__, "slack_sdk.web.slack_response") 7 | -------------------------------------------------------------------------------- /slack/webhook/__init__.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.webhook.webhook_response import WebhookResponse # noqa 2 | from slack_sdk.webhook.client import WebhookClient # noqa 3 | from slack_sdk.webhook.async_client import AsyncWebhookClient # noqa 4 | 5 | from slack import deprecation 6 | 7 | deprecation.show_message(__name__, "slack_sdk.webhook") 8 | -------------------------------------------------------------------------------- /slack/webhook/internal_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional, Dict 3 | 4 | from slack.web import get_user_agent, convert_bool_to_0_or_1 5 | from slack.web.internal_utils import _parse_web_class_objects 6 | from slack.webhook import WebhookResponse 7 | 8 | 9 | def _build_body(original_body: Dict[str, any]) -> Dict[str, any]: 10 | body = {k: v for k, v in original_body.items() if v is not None} 11 | body = convert_bool_to_0_or_1(body) 12 | _parse_web_class_objects(body) 13 | return body 14 | 15 | 16 | def _build_request_headers( 17 | default_headers: Dict[str, str], 18 | additional_headers: Optional[Dict[str, str]], 19 | ) -> Dict[str, str]: 20 | if additional_headers is None: 21 | return {} 22 | 23 | request_headers = { 24 | "User-Agent": get_user_agent(), 25 | "Content-Type": "application/json;charset=utf-8", 26 | } 27 | request_headers.update(default_headers) 28 | if additional_headers: 29 | request_headers.update(additional_headers) 30 | return request_headers 31 | 32 | 33 | def _debug_log_response(logger, resp: WebhookResponse) -> None: 34 | if logger.level <= logging.DEBUG: 35 | logger.debug( 36 | "Received the following response - " 37 | f"status: {resp.status_code}, " 38 | f"headers: {(dict(resp.headers))}, " 39 | f"body: {resp.body}" 40 | ) 41 | -------------------------------------------------------------------------------- /slack/webhook/webhook_response.py: -------------------------------------------------------------------------------- 1 | class WebhookResponse: 2 | def __init__( 3 | self, 4 | *, 5 | url: str, 6 | status_code: int, 7 | body: str, 8 | headers: dict, 9 | ): 10 | self.api_url = url 11 | self.status_code = status_code 12 | self.body = body 13 | self.headers = headers 14 | -------------------------------------------------------------------------------- /slack_sdk/aiohttp_version_checker.py: -------------------------------------------------------------------------------- 1 | """Internal module for checking aiohttp compatibility of async modules""" 2 | import logging 3 | from typing import Callable 4 | 5 | 6 | def _print_warning_log(message: str) -> None: 7 | logging.getLogger(__name__).warning(message) 8 | 9 | 10 | def validate_aiohttp_version( 11 | aiohttp_version: str, 12 | print_warning: Callable[[str], None] = _print_warning_log, 13 | ): 14 | if aiohttp_version is not None: 15 | elements = aiohttp_version.split(".") 16 | if len(elements) >= 3: 17 | # patch version can be a non-numeric value 18 | major, minor, patch = int(elements[0]), int(elements[1]), elements[2] 19 | if major <= 2 or (major == 3 and (minor == 6 or (minor == 7 and patch == "0"))): 20 | print_warning( 21 | "We highly recommend upgrading aiohttp to 3.7.3 or higher versions." 22 | "An older version of the library may not work with the Slack server-side in the future." 23 | ) 24 | -------------------------------------------------------------------------------- /slack_sdk/audit_logs/__init__.py: -------------------------------------------------------------------------------- 1 | """Audit Logs API is a set of APIs for monitoring what’s happening in your Enterprise Grid organization. 2 | 3 | Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details. 4 | """ 5 | from .v1.client import AuditLogsClient 6 | from .v1.response import AuditLogsResponse 7 | 8 | __all__ = [ 9 | "AuditLogsClient", 10 | "AuditLogsResponse", 11 | ] 12 | -------------------------------------------------------------------------------- /slack_sdk/audit_logs/async_client.py: -------------------------------------------------------------------------------- 1 | from .v1.async_client import AsyncAuditLogsClient 2 | 3 | __all__ = [ 4 | "AsyncAuditLogsClient", 5 | ] 6 | -------------------------------------------------------------------------------- /slack_sdk/audit_logs/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """Audit Logs API is a set of APIs for monitoring what’s happening in your Enterprise Grid organization. 2 | 3 | Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details. 4 | """ 5 | -------------------------------------------------------------------------------- /slack_sdk/audit_logs/v1/internal_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional, Dict, Any 3 | from urllib.parse import quote 4 | 5 | from slack_sdk.web.internal_utils import get_user_agent 6 | from .response import AuditLogsResponse 7 | 8 | 9 | def _build_query(params: Optional[Dict[str, Any]]) -> str: 10 | if params is not None and len(params) > 0: 11 | return "&".join({f"{quote(str(k))}={quote(str(v))}" for k, v in params.items() if v is not None}) 12 | return "" 13 | 14 | 15 | def _build_request_headers( 16 | token: str, 17 | default_headers: Dict[str, str], 18 | additional_headers: Optional[Dict[str, str]], 19 | ) -> Dict[str, str]: 20 | request_headers = { 21 | "Content-Type": "application/json;charset=utf-8", 22 | "Authorization": f"Bearer {token}", 23 | } 24 | if default_headers is None or "User-Agent" not in default_headers: 25 | request_headers["User-Agent"] = get_user_agent() 26 | if default_headers is not None: 27 | request_headers.update(default_headers) 28 | if additional_headers is not None: 29 | request_headers.update(additional_headers) 30 | return request_headers 31 | 32 | 33 | def _debug_log_response(logger, resp: AuditLogsResponse) -> None: 34 | if logger.level <= logging.DEBUG: 35 | logger.debug( 36 | "Received the following response - " 37 | f"status: {resp.status_code}, " 38 | f"headers: {(dict(resp.headers))}, " 39 | f"body: {resp.raw_body}" 40 | ) 41 | -------------------------------------------------------------------------------- /slack_sdk/audit_logs/v1/response.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Dict, Any, Optional 3 | 4 | from slack_sdk.audit_logs.v1.logs import LogsResponse 5 | 6 | 7 | # TODO: Unlike WebClient's responses, this class has not yet provided __iter__ method 8 | class AuditLogsResponse: 9 | url: str 10 | status_code: int 11 | headers: Dict[str, Any] 12 | raw_body: Optional[str] 13 | body: Optional[Dict[str, Any]] 14 | typed_body: Optional[LogsResponse] 15 | 16 | @property # type: ignore[no-redef] 17 | def typed_body(self) -> Optional[LogsResponse]: 18 | if self.body is None: 19 | return None 20 | return LogsResponse(**self.body) 21 | 22 | def __init__( 23 | self, 24 | *, 25 | url: str, 26 | status_code: int, 27 | raw_body: Optional[str], 28 | headers: dict, 29 | ): 30 | self.url = url 31 | self.status_code = status_code 32 | self.headers = headers 33 | self.raw_body = raw_body 34 | self.body = json.loads(raw_body) if raw_body is not None and raw_body.startswith("{") else None 35 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from .handler import RetryHandler 4 | from .builtin_handlers import ( 5 | ConnectionErrorRetryHandler, 6 | RateLimitErrorRetryHandler, 7 | ) 8 | from .interval_calculator import RetryIntervalCalculator 9 | from .builtin_interval_calculators import ( 10 | FixedValueRetryIntervalCalculator, 11 | BackoffRetryIntervalCalculator, 12 | ) 13 | from .jitter import Jitter 14 | from .request import HttpRequest 15 | from .response import HttpResponse 16 | from .state import RetryState 17 | 18 | connect_error_retry_handler = ConnectionErrorRetryHandler() 19 | rate_limit_error_retry_handler = RateLimitErrorRetryHandler() 20 | 21 | 22 | def default_retry_handlers() -> List[RetryHandler]: 23 | return [connect_error_retry_handler] 24 | 25 | 26 | def all_builtin_retry_handlers() -> List[RetryHandler]: 27 | return [ 28 | connect_error_retry_handler, 29 | rate_limit_error_retry_handler, 30 | ] 31 | 32 | 33 | __all__ = [ 34 | "RetryHandler", 35 | "ConnectionErrorRetryHandler", 36 | "RateLimitErrorRetryHandler", 37 | "RetryIntervalCalculator", 38 | "FixedValueRetryIntervalCalculator", 39 | "BackoffRetryIntervalCalculator", 40 | "Jitter", 41 | "HttpRequest", 42 | "HttpResponse", 43 | "RetryState", 44 | "connect_error_retry_handler", 45 | "rate_limit_error_retry_handler", 46 | "default_retry_handlers", 47 | "all_builtin_retry_handlers", 48 | ] 49 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/interval_calculator.py: -------------------------------------------------------------------------------- 1 | class RetryIntervalCalculator: 2 | """Retry interval calculator interface.""" 3 | 4 | def calculate_sleep_duration(self, current_attempt: int) -> float: 5 | """Calculates an interval duration in seconds. 6 | 7 | Args: 8 | current_attempt: the number of the current attempt (zero-origin; 0 means no retries are done so far) 9 | Returns: 10 | calculated interval duration in seconds 11 | """ 12 | raise NotImplementedError() 13 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/jitter.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Jitter: 5 | """Jitter interface""" 6 | 7 | def recalculate(self, duration: float) -> float: 8 | """Recalculate the given duration. 9 | see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ 10 | 11 | Args: 12 | duration: the duration in seconds 13 | 14 | Returns: 15 | A new duration that the jitter amount is added 16 | """ 17 | raise NotImplementedError() 18 | 19 | 20 | class RandomJitter(Jitter): 21 | """Random jitter implementation""" 22 | 23 | def recalculate(self, duration: float) -> float: 24 | return duration + random.random() 25 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/request.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, List, Union, Any 2 | from urllib.request import Request 3 | 4 | 5 | class HttpRequest: 6 | """HTTP request representation""" 7 | 8 | method: str 9 | url: str 10 | headers: Dict[str, Union[str, List[str]]] 11 | body_params: Optional[Dict[str, Any]] 12 | data: Optional[bytes] 13 | 14 | def __init__( 15 | self, 16 | *, 17 | method: str, 18 | url: str, 19 | headers: Dict[str, Union[str, List[str]]], 20 | body_params: Optional[Dict[str, Any]] = None, 21 | data: Optional[bytes] = None, 22 | ): 23 | self.method = method 24 | self.url = url 25 | self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()} 26 | self.body_params = body_params 27 | self.data = data 28 | 29 | @classmethod 30 | def from_urllib_http_request(cls, req: Request) -> "HttpRequest": 31 | return HttpRequest( 32 | method=req.method, # type: ignore[arg-type] 33 | url=req.full_url, 34 | headers={k: v if isinstance(v, list) else [v] for k, v in req.headers.items()}, 35 | data=req.data, # type: ignore[arg-type] 36 | ) 37 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/response.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, List, Union, Any 2 | 3 | 4 | class HttpResponse: 5 | """HTTP response representation""" 6 | 7 | status_code: int 8 | headers: Dict[str, Union[List[str], str]] 9 | body: Optional[Dict[str, Any]] 10 | data: Optional[bytes] 11 | 12 | def __init__( 13 | self, 14 | *, 15 | status_code: Union[int, str], 16 | headers: Dict[str, Union[str, List[str]]], 17 | body: Optional[Dict[str, Any]] = None, 18 | data: Optional[bytes] = None, 19 | ): 20 | self.status_code = int(status_code) 21 | self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()} 22 | self.body = body 23 | self.data = data 24 | -------------------------------------------------------------------------------- /slack_sdk/http_retry/state.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, Dict 2 | 3 | 4 | class RetryState: 5 | next_attempt_requested: bool 6 | current_attempt: int # zero-origin 7 | custom_values: Optional[Dict[str, Any]] 8 | 9 | def __init__( 10 | self, 11 | *, 12 | current_attempt: int = 0, 13 | custom_values: Optional[Dict[str, Any]] = None, 14 | ): 15 | self.next_attempt_requested = False 16 | self.current_attempt = current_attempt 17 | self.custom_values = custom_values 18 | 19 | def increment_current_attempt(self) -> int: 20 | self.current_attempt += 1 21 | return self.current_attempt 22 | -------------------------------------------------------------------------------- /slack_sdk/models/dialoags.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.models.dialogs import AbstractDialogSelector 2 | from slack_sdk.models.dialogs import DialogChannelSelector 3 | from slack_sdk.models.dialogs import DialogConversationSelector 4 | from slack_sdk.models.dialogs import DialogExternalSelector 5 | from slack_sdk.models.dialogs import DialogStaticSelector 6 | from slack_sdk.models.dialogs import DialogTextArea 7 | from slack_sdk.models.dialogs import DialogTextComponent 8 | from slack_sdk.models.dialogs import DialogTextField 9 | from slack_sdk.models.dialogs import DialogUserSelector 10 | from slack_sdk.models.dialogs import TextElementSubtypes 11 | from slack_sdk.models.dialogs import DialogBuilder 12 | 13 | from slack import deprecation 14 | 15 | deprecation.show_message(__name__, "slack_sdk.models.dialogs") 16 | 17 | __all__ = [ 18 | "AbstractDialogSelector", 19 | "DialogChannelSelector", 20 | "DialogConversationSelector", 21 | "DialogExternalSelector", 22 | "DialogStaticSelector", 23 | "DialogTextArea", 24 | "DialogTextComponent", 25 | "DialogTextField", 26 | "DialogUserSelector", 27 | "TextElementSubtypes", 28 | "DialogBuilder", 29 | ] 30 | -------------------------------------------------------------------------------- /slack_sdk/models/metadata/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | from slack_sdk.models.basic_objects import JsonObject 3 | 4 | 5 | class Metadata(JsonObject): 6 | """Message metadata 7 | 8 | https://api.slack.com/metadata 9 | """ 10 | 11 | attributes = { 12 | "event_type", 13 | "event_payload", 14 | } 15 | 16 | def __init__( 17 | self, 18 | event_type: str, 19 | event_payload: Dict[str, Any], 20 | **kwargs, 21 | ): 22 | self.event_type = event_type 23 | self.event_payload = event_payload 24 | self.additional_attributes = kwargs 25 | 26 | def __str__(self): 27 | return str(self.get_non_null_attributes()) 28 | 29 | def __repr__(self): 30 | return self.__str__() 31 | -------------------------------------------------------------------------------- /slack_sdk/oauth/__init__.py: -------------------------------------------------------------------------------- 1 | """Modules for implementing the Slack OAuth flow 2 | 3 | https://slack.dev/python-slack-sdk/oauth/ 4 | """ 5 | from .authorize_url_generator import AuthorizeUrlGenerator 6 | from .authorize_url_generator import OpenIDConnectAuthorizeUrlGenerator 7 | from .installation_store import InstallationStore 8 | from .redirect_uri_page_renderer import RedirectUriPageRenderer 9 | from .state_store import OAuthStateStore 10 | from .state_utils import OAuthStateUtils 11 | 12 | __all__ = [ 13 | "AuthorizeUrlGenerator", 14 | "OpenIDConnectAuthorizeUrlGenerator", 15 | "InstallationStore", 16 | "RedirectUriPageRenderer", 17 | "OAuthStateStore", 18 | "OAuthStateUtils", 19 | ] 20 | -------------------------------------------------------------------------------- /slack_sdk/oauth/installation_store/__init__.py: -------------------------------------------------------------------------------- 1 | from .file import FileInstallationStore 2 | from .installation_store import InstallationStore 3 | from .models import Bot, Installation 4 | 5 | __all__ = [ 6 | "FileInstallationStore", 7 | "InstallationStore", 8 | "Bot", 9 | "Installation", 10 | ] 11 | -------------------------------------------------------------------------------- /slack_sdk/oauth/installation_store/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .bot import Bot 2 | from .installation import Installation 3 | 4 | __all__ = [ 5 | "Bot", 6 | "Installation", 7 | ] 8 | -------------------------------------------------------------------------------- /slack_sdk/oauth/state_store/__init__.py: -------------------------------------------------------------------------------- 1 | """OAuth state parameter data store 2 | 3 | Refer to https://slack.dev/python-slack-sdk/oauth/ for details. 4 | """ 5 | # from .amazon_s3_state_store import AmazonS3OAuthStateStore 6 | from .file import FileOAuthStateStore 7 | from .state_store import OAuthStateStore 8 | 9 | __all__ = [ 10 | "FileOAuthStateStore", 11 | "OAuthStateStore", 12 | ] 13 | -------------------------------------------------------------------------------- /slack_sdk/oauth/state_store/async_state_store.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | 3 | 4 | class AsyncOAuthStateStore: 5 | @property 6 | def logger(self) -> Logger: 7 | raise NotImplementedError() 8 | 9 | async def async_issue(self, *args, **kwargs) -> str: 10 | raise NotImplementedError() 11 | 12 | async def async_consume(self, state: str) -> bool: 13 | raise NotImplementedError() 14 | -------------------------------------------------------------------------------- /slack_sdk/oauth/state_store/state_store.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | 3 | 4 | class OAuthStateStore: 5 | @property 6 | def logger(self) -> Logger: 7 | raise NotImplementedError() 8 | 9 | def issue(self, *args, **kwargs) -> str: 10 | raise NotImplementedError() 11 | 12 | def consume(self, state: str) -> bool: 13 | raise NotImplementedError() 14 | -------------------------------------------------------------------------------- /slack_sdk/oauth/token_rotation/__init__.py: -------------------------------------------------------------------------------- 1 | from .rotator import TokenRotator 2 | 3 | __all__ = [ 4 | "TokenRotator", 5 | ] 6 | -------------------------------------------------------------------------------- /slack_sdk/proxy_env_variable_loader.py: -------------------------------------------------------------------------------- 1 | """Internal module for loading proxy-related env variables""" 2 | import logging 3 | import os 4 | from typing import Optional 5 | 6 | _default_logger = logging.getLogger(__name__) 7 | 8 | 9 | def load_http_proxy_from_env(logger: logging.Logger = _default_logger) -> Optional[str]: 10 | proxy_url = ( 11 | os.environ.get("HTTPS_PROXY") 12 | or os.environ.get("https_proxy") 13 | or os.environ.get("HTTP_PROXY") 14 | or os.environ.get("http_proxy") 15 | ) 16 | if proxy_url is None: 17 | return None 18 | if len(proxy_url.strip()) == 0: 19 | # If the value is an empty string, the intention should be unsetting it 20 | logger.debug("The Slack SDK ignored the proxy env variable as an empty value is set.") 21 | return None 22 | 23 | logger.debug(f"HTTP proxy URL has been loaded from an env variable: {proxy_url}") 24 | return proxy_url 25 | -------------------------------------------------------------------------------- /slack_sdk/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/slack_sdk/py.typed -------------------------------------------------------------------------------- /slack_sdk/rtm/v2/__init__.py: -------------------------------------------------------------------------------- 1 | from slack_sdk.rtm_v2 import RTMClient 2 | 3 | __all__ = [ 4 | "RTMClient", 5 | ] 6 | -------------------------------------------------------------------------------- /slack_sdk/scim/__init__.py: -------------------------------------------------------------------------------- 1 | """SCIM API is a set of APIs for provisioning and managing user accounts and groups. 2 | SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, 3 | including Slack. 4 | 5 | Refer to https://slack.dev/python-slack-sdk/scim/ for details. 6 | """ 7 | from .v1.client import SCIMClient 8 | from .v1.response import SCIMResponse 9 | from .v1.response import SearchUsersResponse, ReadUserResponse 10 | from .v1.response import SearchGroupsResponse, ReadGroupResponse 11 | from .v1.user import User 12 | from .v1.group import Group 13 | 14 | __all__ = [ 15 | "SCIMClient", 16 | "SCIMResponse", 17 | "SearchUsersResponse", 18 | "ReadUserResponse", 19 | "SearchGroupsResponse", 20 | "ReadGroupResponse", 21 | "User", 22 | "Group", 23 | ] 24 | -------------------------------------------------------------------------------- /slack_sdk/scim/async_client.py: -------------------------------------------------------------------------------- 1 | from .v1.async_client import AsyncSCIMClient 2 | 3 | __all__ = [ 4 | "AsyncSCIMClient", 5 | ] 6 | -------------------------------------------------------------------------------- /slack_sdk/scim/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """SCIM API is a set of APIs for provisioning and managing user accounts and groups. 2 | SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, 3 | including Slack. 4 | 5 | Refer to https://slack.dev/python-slack-sdk/scim/ for details. 6 | """ 7 | -------------------------------------------------------------------------------- /slack_sdk/scim/v1/default_arg.py: -------------------------------------------------------------------------------- 1 | class DefaultArg: 2 | pass 3 | 4 | 5 | NotGiven = DefaultArg() 6 | -------------------------------------------------------------------------------- /slack_sdk/scim/v1/types.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, Dict, Any 2 | 3 | from .default_arg import DefaultArg, NotGiven 4 | from .internal_utils import _to_dict_without_not_given 5 | 6 | 7 | class TypeAndValue: 8 | primary: Union[Optional[bool], DefaultArg] 9 | type: Union[Optional[str], DefaultArg] 10 | value: Union[Optional[str], DefaultArg] 11 | unknown_fields: Dict[str, Any] 12 | 13 | def __init__( 14 | self, 15 | *, 16 | primary: Union[Optional[bool], DefaultArg] = NotGiven, 17 | type: Union[Optional[str], DefaultArg] = NotGiven, 18 | value: Union[Optional[str], DefaultArg] = NotGiven, 19 | **kwargs, 20 | ) -> None: 21 | self.primary = primary 22 | self.type = type 23 | self.value = value 24 | self.unknown_fields = kwargs 25 | 26 | def to_dict(self) -> dict: 27 | return _to_dict_without_not_given(self) 28 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/__init__.py: -------------------------------------------------------------------------------- 1 | """Socket Mode is a method of connecting your app to Slack’s APIs using WebSockets instead of HTTP. 2 | You can use slack_sdk.socket_mode.SocketModeClient for managing Socket Mode connections 3 | and performing interactions with Slack. 4 | 5 | https://api.slack.com/apis/connections/socket 6 | """ 7 | from .builtin import SocketModeClient 8 | 9 | __all__ = [ 10 | "SocketModeClient", 11 | ] 12 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/async_listeners.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Callable 2 | 3 | from slack_sdk.socket_mode.request import SocketModeRequest 4 | 5 | 6 | class AsyncWebSocketMessageListener(Callable): # type: ignore[misc] 7 | async def __call__( 8 | client: "AsyncBaseSocketModeClient", # type: ignore[name-defined] # noqa: F821 9 | message: dict, 10 | raw_message: Optional[str] = None, 11 | ): # noqa: F821 12 | raise NotImplementedError() 13 | 14 | 15 | class AsyncSocketModeRequestListener(Callable): # type: ignore[misc] 16 | async def __call__( 17 | client: "AsyncBaseSocketModeClient", # type: ignore[name-defined] # noqa: F821 18 | request: SocketModeRequest, 19 | ): # noqa: F821 20 | raise NotImplementedError() 21 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/builtin/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import SocketModeClient 2 | 3 | __all__ = [ 4 | "SocketModeClient", 5 | ] 6 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/builtin/frame_header.py: -------------------------------------------------------------------------------- 1 | class FrameHeader: 2 | fin: int 3 | rsv1: int 4 | rsv2: int 5 | rsv3: int 6 | opcode: int 7 | masked: int 8 | length: int 9 | 10 | # Opcode 11 | # https://tools.ietf.org/html/rfc6455#section-5.2 12 | # Non-control frames 13 | # %x0 denotes a continuation frame 14 | OPCODE_CONTINUATION = 0x0 15 | # %x1 denotes a text frame 16 | OPCODE_TEXT = 0x1 17 | # %x2 denotes a binary frame 18 | OPCODE_BINARY = 0x2 19 | # %x3-7 are reserved for further non-control frames 20 | 21 | # Control frames 22 | # %x8 denotes a connection close 23 | OPCODE_CLOSE = 0x8 24 | # %x9 denotes a ping 25 | OPCODE_PING = 0x9 26 | # %xA denotes a pong 27 | OPCODE_PONG = 0xA 28 | 29 | # %xB-F are reserved for further control frames 30 | 31 | def __init__( 32 | self, 33 | opcode: int, 34 | fin: int = 1, 35 | rsv1: int = 0, 36 | rsv2: int = 0, 37 | rsv3: int = 0, 38 | masked: int = 0, 39 | length: int = 0, 40 | ): 41 | self.opcode = opcode 42 | self.fin = fin 43 | self.rsv1 = rsv1 44 | self.rsv2 = rsv2 45 | self.rsv3 = rsv3 46 | self.masked = masked 47 | self.length = length 48 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/interval_runner.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from threading import Thread, Event 3 | from typing import Callable 4 | 5 | 6 | class IntervalRunner: 7 | event: Event 8 | thread: Thread 9 | 10 | def __init__(self, target: Callable[[], None], interval_seconds: float = 0.1): 11 | self.event = threading.Event() 12 | self.target = target 13 | self.interval_seconds = interval_seconds 14 | self.thread = threading.Thread(target=self._run) 15 | self.thread.daemon = True 16 | 17 | def _run(self) -> None: 18 | while not self.event.is_set(): 19 | self.target() 20 | self.event.wait(self.interval_seconds) 21 | 22 | def start(self) -> "IntervalRunner": 23 | self.thread.start() 24 | return self 25 | 26 | def is_alive(self) -> bool: 27 | return self.thread is not None and self.thread.is_alive() 28 | 29 | def shutdown(self): 30 | if self.is_alive(): 31 | self.event.set() 32 | self.thread.join() 33 | self.thread = None 34 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/listeners.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from slack_sdk.socket_mode.request import SocketModeRequest 4 | 5 | 6 | class WebSocketMessageListener: 7 | def __call__( 8 | client: "BaseSocketModeClient", # type: ignore[name-defined] # noqa: F821 9 | message: dict, 10 | raw_message: Optional[str] = None, 11 | ): # noqa: F821 12 | raise NotImplementedError() 13 | 14 | 15 | class SocketModeRequestListener: 16 | def __call__(client: "BaseSocketModeClient", request: SocketModeRequest): # type: ignore[name-defined] # noqa: F821, F821, E501 17 | raise NotImplementedError() 18 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/logger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/slack_sdk/socket_mode/logger/__init__.py -------------------------------------------------------------------------------- /slack_sdk/socket_mode/logger/messages.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def debug_redacted_message_string(message: str) -> str: 5 | xwfp_token_pattern = re.compile(r"\"xwfp-[A-Za-z0-9\-]+\"") # ex: "xwfp-abc-ABC-1234" 6 | return re.sub(xwfp_token_pattern, "[[REDACTED]]", message) 7 | -------------------------------------------------------------------------------- /slack_sdk/socket_mode/response.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional 2 | 3 | from slack_sdk.models import JsonObject 4 | 5 | 6 | class SocketModeResponse: 7 | envelope_id: str 8 | payload: Optional[dict] 9 | 10 | def __init__(self, envelope_id: str, payload: Optional[Union[dict, JsonObject, str]] = None): 11 | self.envelope_id = envelope_id 12 | 13 | if payload is None: 14 | self.payload = None 15 | elif isinstance(payload, JsonObject): 16 | self.payload = payload.to_dict() 17 | elif isinstance(payload, dict): 18 | self.payload = payload 19 | elif isinstance(payload, str): 20 | self.payload = {"text": payload} 21 | else: 22 | raise ValueError(f"Unsupported payload data type ({type(payload)})") 23 | 24 | def to_dict(self) -> dict: 25 | d = {"envelope_id": self.envelope_id} 26 | if self.payload is not None: 27 | d["payload"] = self.payload # type: ignore[assignment] 28 | return d 29 | -------------------------------------------------------------------------------- /slack_sdk/version.py: -------------------------------------------------------------------------------- 1 | """Check the latest version at https://pypi.org/project/slack-sdk/""" 2 | 3 | __version__ = "3.35.0" 4 | -------------------------------------------------------------------------------- /slack_sdk/web/__init__.py: -------------------------------------------------------------------------------- 1 | """The Slack Web API allows you to build applications that interact with Slack 2 | in more complex ways than the integrations we provide out of the box.""" 3 | from .client import WebClient 4 | from .slack_response import SlackResponse 5 | 6 | __all__ = [ 7 | "WebClient", 8 | "SlackResponse", 9 | ] 10 | -------------------------------------------------------------------------------- /slack_sdk/web/file_upload_v2_result.py: -------------------------------------------------------------------------------- 1 | class FileUploadV2Result: 2 | status: int 3 | body: str 4 | 5 | def __init__(self, status: int, body: str): 6 | self.status = status 7 | self.body = body 8 | -------------------------------------------------------------------------------- /slack_sdk/webhook/__init__.py: -------------------------------------------------------------------------------- 1 | """You can use slack_sdk.webhook.WebhookClient for Incoming Webhooks 2 | and message responses using response_url in payloads. 3 | """ 4 | # from .async_client import AsyncWebhookClient 5 | from .client import WebhookClient 6 | from .webhook_response import WebhookResponse 7 | 8 | __all__ = [ 9 | "WebhookClient", 10 | "WebhookResponse", 11 | ] 12 | -------------------------------------------------------------------------------- /slack_sdk/webhook/internal_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional, Dict, Any 3 | 4 | from slack_sdk.web.internal_utils import ( 5 | _parse_web_class_objects, 6 | get_user_agent, 7 | ) 8 | from .webhook_response import WebhookResponse 9 | 10 | 11 | def _build_body(original_body: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: 12 | if original_body: 13 | body = {k: v for k, v in original_body.items() if v is not None} 14 | _parse_web_class_objects(body) 15 | return body 16 | return None 17 | 18 | 19 | def _build_request_headers( 20 | default_headers: Dict[str, str], 21 | additional_headers: Optional[Dict[str, str]], 22 | ) -> Dict[str, str]: 23 | if default_headers is None and additional_headers is None: 24 | return {} 25 | 26 | request_headers = { 27 | "Content-Type": "application/json;charset=utf-8", 28 | } 29 | if default_headers is None or "User-Agent" not in default_headers: 30 | request_headers["User-Agent"] = get_user_agent() 31 | 32 | request_headers.update(default_headers) 33 | if additional_headers: 34 | request_headers.update(additional_headers) 35 | return request_headers 36 | 37 | 38 | def _debug_log_response(logger, resp: WebhookResponse) -> None: 39 | if logger.level <= logging.DEBUG: 40 | logger.debug( 41 | "Received the following response - " 42 | f"status: {resp.status_code}, " 43 | f"headers: {(dict(resp.headers))}, " 44 | f"body: {resp.body}" 45 | ) 46 | -------------------------------------------------------------------------------- /slack_sdk/webhook/webhook_response.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | 4 | class WebhookResponse: 5 | def __init__( 6 | self, 7 | *, 8 | url: str, 9 | status_code: int, 10 | body: str, 11 | headers: Dict[str, Any], 12 | ): 13 | self.api_url = url 14 | self.status_code = status_code 15 | self.body = body 16 | self.headers = headers 17 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/channel.created.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "channel_created", 3 | "channel": { 4 | "id": "C024BE91L", 5 | "name": "fun", 6 | "created": 1360782804, 7 | "creator": "U024BE7LH" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/im.created.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "im_created", 3 | "user": "U024BE7LH", 4 | "channel": { 5 | "id": "D024BE91L", 6 | "user": "U123BL234", 7 | "created": 1360782804 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/slack_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/data/slack_logo.png -------------------------------------------------------------------------------- /tests/data/slack_logo_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/data/slack_logo_new.png -------------------------------------------------------------------------------- /tests/data/view_home_006.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VMHU10V25", 3 | "team_id": "T8N4K1JN", 4 | "type": "home", 5 | "blocks": [ 6 | { 7 | "type": "section", 8 | "block_id": "2WGp9", 9 | "text": { 10 | "type": "mrkdwn", 11 | "text": "A simple section with some sample sentence.", 12 | "verbatim": false 13 | } 14 | } 15 | ], 16 | "private_metadata": "Shh it is a secret", 17 | "callback_id": "identify_your_home_tab", 18 | "hash": "156772938.1827394", 19 | "clear_on_close": false, 20 | "notify_on_close": false, 21 | "root_view_id": "VMHU10V25", 22 | "app_id": "AA4928AQ", 23 | "external_id": "some-unique-id", 24 | "bot_id": "BA13894H" 25 | } -------------------------------------------------------------------------------- /tests/data/view_modal_008.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "modal", 3 | "title": { 4 | "type": "plain_text", 5 | "text": "My App", 6 | "emoji": true 7 | }, 8 | "submit": { 9 | "type": "plain_text", 10 | "text": "Submit", 11 | "emoji": true 12 | }, 13 | "close": { 14 | "type": "plain_text", 15 | "text": "Cancel", 16 | "emoji": true 17 | }, 18 | "private_metadata": "something important here", 19 | "blocks": [ 20 | { 21 | "type": "section", 22 | "text": { 23 | "type": "mrkdwn", 24 | "text": "This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and " 25 | } 26 | } 27 | ], 28 | "state": { 29 | "values": { 30 | "multi-line": { 31 | "ml-value": { 32 | "type": "plain_text_input", 33 | "value": "This is my example inputted value" 34 | } 35 | } 36 | } 37 | }, 38 | "hash": "156663117.cd33ad1f" 39 | } -------------------------------------------------------------------------------- /tests/data/view_modal_009.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VNM522E2U", 3 | "team_id": "T9M4RL1JM", 4 | "type": "modal", 5 | "title": { 6 | "type": "plain_text", 7 | "text": "Pushed Modal", 8 | "emoji": true 9 | }, 10 | "close": { 11 | "type": "plain_text", 12 | "text": "Back", 13 | "emoji": true 14 | }, 15 | "submit": { 16 | "type": "plain_text", 17 | "text": "Save", 18 | "emoji": true 19 | }, 20 | "blocks": [ 21 | { 22 | "type": "input", 23 | "block_id": "edit_details", 24 | "element": { 25 | "type": "plain_text_input", 26 | "action_id": "detail_input" 27 | }, 28 | "label": { 29 | "type": "plain_text", 30 | "text": "Edit details" 31 | } 32 | } 33 | ], 34 | "private_metadata": "secret", 35 | "callback_id": "view_4", 36 | "external_id": "some-unique-id", 37 | "state": { 38 | "values": {} 39 | }, 40 | "hash": "1569362015.55b5e41b", 41 | "clear_on_close": true, 42 | "notify_on_close": false, 43 | "root_view_id": "VNN729E3U", 44 | "app_id": "AAD3351BQ", 45 | "bot_id": "BADF7A34H" 46 | } -------------------------------------------------------------------------------- /tests/data/view_modal_010.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VMHU10V25", 3 | "team_id": "T8N4K1JN", 4 | "type": "modal", 5 | "title": { 6 | "type": "plain_text", 7 | "text": "Quite a plain modal" 8 | }, 9 | "submit": { 10 | "type": "plain_text", 11 | "text": "Create" 12 | }, 13 | "blocks": [ 14 | { 15 | "type": "input", 16 | "block_id": "a_block_id", 17 | "label": { 18 | "type": "plain_text", 19 | "text": "A simple label", 20 | "emoji": true 21 | }, 22 | "optional": false, 23 | "element": { 24 | "type": "plain_text_input", 25 | "action_id": "an_action_id" 26 | } 27 | } 28 | ], 29 | "private_metadata": "Shh it is a secret", 30 | "callback_id": "identify_your_modals", 31 | "external_id": "some-unique-id", 32 | "state": { 33 | "values": {} 34 | }, 35 | "hash": "156772938.1827394", 36 | "clear_on_close": false, 37 | "notify_on_close": false, 38 | "root_view_id": "VMHU10V25", 39 | "app_id": "AA4928AQ", 40 | "bot_id": "BA13894H" 41 | } -------------------------------------------------------------------------------- /tests/data/web_response_api_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/data/web_response_api_test_false.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": false 3 | } -------------------------------------------------------------------------------- /tests/data/web_response_channels_list_pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C1" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "has_page2" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/data/web_response_channels_list_pagination2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C2" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "page2" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/data/web_response_channels_list_pagination2_page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C3" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/data/web_response_channels_list_pagination_has_page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C2" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "has_page3" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/data/web_response_channels_list_pagination_has_page3.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C3" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/data/web_response_users_list_pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "members": [ 4 | "Bob", 5 | "cat" 6 | ], 7 | "response_metadata": { 8 | "next_cursor": 1 9 | } 10 | } -------------------------------------------------------------------------------- /tests/data/web_response_users_list_pagination_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "members": [ 4 | "Kevin", 5 | "dog" 6 | ], 7 | "response_metadata": { 8 | "next_cursor": "" 9 | } 10 | } -------------------------------------------------------------------------------- /tests/data/web_response_users_setPhoto.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/data/日本語.txt: -------------------------------------------------------------------------------- 1 | 日本語の文書です。 -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import copy 3 | import os 4 | import sys 5 | from typing import Any 6 | 7 | 8 | def async_test(coro): 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | 12 | def wrapper(*args, **kwargs): 13 | future = coro(*args, **kwargs) 14 | return asyncio.get_event_loop().run_until_complete(future) 15 | 16 | return wrapper 17 | 18 | 19 | def remove_os_env_temporarily() -> dict: 20 | old_env = os.environ.copy() 21 | os.environ.clear() 22 | for key, value in old_env.items(): 23 | if key.startswith("PYTHON_SLACK_SDK_"): 24 | os.environ[key] = value 25 | return old_env 26 | 27 | 28 | def restore_os_env(old_env: dict) -> None: 29 | os.environ.update(old_env) 30 | 31 | 32 | def create_copy(original: Any) -> Any: 33 | if sys.version_info.major == 3 and sys.version_info.minor <= 6: 34 | # NOTE: Unfortunately, copy.deepcopy doesn't work in Python 3.6.5. 35 | # -------------------- 36 | # > rv = reductor(4) 37 | # E TypeError: can't pickle _thread.RLock objects 38 | # ../../.pyenv/versions/3.6.10/lib/python3.6/copy.py:169: TypeError 39 | # -------------------- 40 | # As a workaround, this operation uses shallow copies in Python 3.6. 41 | # If your code modifies the shared data in threads / async functions, race conditions may arise. 42 | # Please consider upgrading Python major version to 3.7+ if you encounter some issues due to this. 43 | return copy.copy(original) 44 | else: 45 | return copy.deepcopy(original) 46 | -------------------------------------------------------------------------------- /tests/mock_web_api_server/mock_server_thread.py: -------------------------------------------------------------------------------- 1 | from asyncio import Queue 2 | import asyncio 3 | from http.server import HTTPServer, SimpleHTTPRequestHandler 4 | import threading 5 | from typing import Type, Union 6 | from unittest import TestCase 7 | 8 | 9 | class MockServerThread(threading.Thread): 10 | def __init__( 11 | self, queue: Union[Queue, asyncio.Queue], test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888 12 | ): 13 | threading.Thread.__init__(self) 14 | self.handler = handler 15 | self.test = test 16 | self.queue = queue 17 | self.port = port 18 | 19 | def run(self): 20 | self.server = HTTPServer(("localhost", self.port), self.handler) 21 | self.server.queue = self.queue 22 | self.test.server_url = f"http://localhost:{str(self.port)}" 23 | self.test.host, self.test.port = self.server.socket.getsockname() 24 | self.test.server_started.set() # threading.Event() 25 | 26 | self.test = None 27 | try: 28 | self.server.serve_forever(0.05) 29 | finally: 30 | self.server.server_close() 31 | 32 | def stop(self): 33 | with self.server.queue.mutex: 34 | del self.server.queue 35 | self.server.shutdown() 36 | self.join() 37 | 38 | def stop_unsafe(self): 39 | del self.server.queue 40 | self.server.shutdown() 41 | self.join() 42 | -------------------------------------------------------------------------------- /tests/mock_web_api_server/received_requests.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from queue import Queue 3 | from typing import Optional, Union 4 | 5 | 6 | class ReceivedRequests: 7 | def __init__(self, queue: Union[Queue, asyncio.Queue]): 8 | self.queue = queue 9 | self.received_requests: dict = {} 10 | 11 | def get(self, key: str, default: Optional[int] = None) -> Optional[int]: 12 | while not self.queue.empty(): 13 | path = self.queue.get() 14 | self.received_requests[path] = self.received_requests.get(path, 0) + 1 15 | return self.received_requests.get(key, default) 16 | 17 | async def get_async(self, key: str, default: Optional[int] = None) -> Optional[int]: 18 | while not self.queue.empty(): 19 | path = await self.queue.get() 20 | self.received_requests[path] = self.received_requests.get(path, 0) + 1 21 | return self.received_requests.get(key, default) 22 | -------------------------------------------------------------------------------- /tests/slack_sdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/audit_logs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/audit_logs/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/audit_logs/test_client_http_retry.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.audit_logs import AuditLogsClient 4 | from slack_sdk.http_retry import RateLimitErrorRetryHandler 5 | from tests.slack_sdk.audit_logs.mock_web_api_handler import MockHandler 6 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 7 | from ..my_retry_handler import MyRetryHandler 8 | 9 | 10 | class TestAuditLogsClient_HttpRetries(unittest.TestCase): 11 | def setUp(self): 12 | setup_mock_web_api_server(self, MockHandler) 13 | 14 | def tearDown(self): 15 | cleanup_mock_web_api_server(self) 16 | 17 | def test_retries(self): 18 | retry_handler = MyRetryHandler(max_retry_count=2) 19 | client = AuditLogsClient( 20 | token="xoxp-remote_disconnected", 21 | base_url="http://localhost:8888/", 22 | retry_handlers=[retry_handler], 23 | ) 24 | try: 25 | client.actions() 26 | self.fail("An exception is expected") 27 | except Exception as _: 28 | pass 29 | 30 | self.assertEqual(2, retry_handler.call_count) 31 | 32 | def test_ratelimited(self): 33 | client = AuditLogsClient( 34 | token="xoxp-ratelimited", 35 | base_url="http://localhost:8888/", 36 | ) 37 | client.retry_handlers.append(RateLimitErrorRetryHandler()) 38 | 39 | response = client.actions() 40 | # Just running retries; no assertions for call count so far 41 | self.assertEqual(429, response.status_code) 42 | -------------------------------------------------------------------------------- /tests/slack_sdk/fatal_error_retry_handler.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator 4 | from slack_sdk.http_retry.state import RetryState 5 | from slack_sdk.http_retry.request import HttpRequest 6 | from slack_sdk.http_retry.response import HttpResponse 7 | from slack_sdk.http_retry.handler import RetryHandler, default_interval_calculator 8 | 9 | 10 | class FatalErrorRetryHandler(RetryHandler): 11 | def __init__( 12 | self, 13 | max_retry_count: int = 1, 14 | interval_calculator: RetryIntervalCalculator = default_interval_calculator, 15 | ): 16 | super().__init__(max_retry_count, interval_calculator) 17 | self.call_count = 0 18 | 19 | def _can_retry( 20 | self, 21 | *, 22 | state: RetryState, 23 | request: HttpRequest, 24 | response: Optional[HttpResponse], 25 | error: Optional[Exception], 26 | ) -> bool: 27 | self.call_count += 1 28 | return response is not None and response.status_code == 200 and response.body.get("error") == "fatal_error" 29 | -------------------------------------------------------------------------------- /tests/slack_sdk/http_retry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/http_retry/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/http_retry/test_builtins.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.http_retry import ( 4 | FixedValueRetryIntervalCalculator, 5 | default_retry_handlers, 6 | all_builtin_retry_handlers, 7 | ) 8 | 9 | 10 | class TestBuiltins(unittest.TestCase): 11 | def test_default_ones(self): 12 | list = default_retry_handlers() 13 | self.assertEqual(1, len(list)) 14 | list.clear() 15 | self.assertEqual(0, len(list)) 16 | list = default_retry_handlers() 17 | self.assertEqual(1, len(list)) 18 | 19 | list = all_builtin_retry_handlers() 20 | self.assertEqual(2, len(list)) 21 | list.clear() 22 | self.assertEqual(0, len(list)) 23 | list = all_builtin_retry_handlers() 24 | self.assertEqual(2, len(list)) 25 | 26 | def test_fixed_value_retry_interval_calculator(self): 27 | for fixed_value in [0.1, 0.2]: 28 | calculator = FixedValueRetryIntervalCalculator(fixed_internal=fixed_value) 29 | for i in range(10): 30 | self.assertEqual(fixed_value, calculator.calculate_sleep_duration(i)) 31 | -------------------------------------------------------------------------------- /tests/slack_sdk/models/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.models import extract_json 4 | from slack_sdk.models.blocks import PlainTextObject, MarkdownTextObject 5 | 6 | 7 | class TestInit(unittest.TestCase): 8 | def test_from_list_of_json_objects(self): 9 | json_objects = [ 10 | PlainTextObject.from_str("foo"), 11 | MarkdownTextObject.from_str("bar"), 12 | ] 13 | output = extract_json(json_objects) 14 | expected = { 15 | "result": [ 16 | {"type": "plain_text", "text": "foo", "emoji": True}, 17 | {"type": "mrkdwn", "text": "bar"}, 18 | ] 19 | } 20 | self.assertDictEqual(expected, {"result": output}) 21 | 22 | def test_from_single_json_object(self): 23 | single_json_object = PlainTextObject.from_str("foo") 24 | output = extract_json(single_json_object) 25 | expected = {"result": {"type": "plain_text", "text": "foo", "emoji": True}} 26 | self.assertDictEqual(expected, {"result": output}) 27 | -------------------------------------------------------------------------------- /tests/slack_sdk/my_retry_handler.py: -------------------------------------------------------------------------------- 1 | from http.client import RemoteDisconnected 2 | from typing import Optional 3 | from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator 4 | from slack_sdk.http_retry.state import RetryState 5 | from slack_sdk.http_retry.request import HttpRequest 6 | from slack_sdk.http_retry.response import HttpResponse 7 | from slack_sdk.http_retry.handler import RetryHandler, default_interval_calculator 8 | 9 | 10 | class MyRetryHandler(RetryHandler): 11 | def __init__( 12 | self, 13 | max_retry_count: int = 1, 14 | interval_calculator: RetryIntervalCalculator = default_interval_calculator, 15 | ): 16 | super().__init__(max_retry_count, interval_calculator) 17 | self.call_count = 0 18 | 19 | def _can_retry( 20 | self, 21 | *, 22 | state: RetryState, 23 | request: HttpRequest, 24 | response: Optional[HttpResponse], 25 | error: Optional[Exception], 26 | ) -> bool: 27 | self.call_count += 1 28 | if error is None: 29 | return False 30 | for error_type in [ConnectionResetError, RemoteDisconnected]: 31 | if isinstance(error, error_type): 32 | return True 33 | return False 34 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/authorize_url_generator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/authorize_url_generator/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/installation_store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/installation_store/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/installation_store/test_interface.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth.installation_store import InstallationStore 4 | from slack_sdk.oauth.installation_store.async_installation_store import ( 5 | AsyncInstallationStore, 6 | ) 7 | 8 | 9 | class TestInterface(unittest.TestCase): 10 | def setUp(self): 11 | pass 12 | 13 | def tearDown(self): 14 | pass 15 | 16 | def test_sync(self): 17 | store = InstallationStore() 18 | self.assertIsNotNone(store) 19 | 20 | def test_async(self): 21 | store = AsyncInstallationStore() 22 | self.assertIsNotNone(store) 23 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/redirect_uri_page_renderer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/redirect_uri_page_renderer/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/redirect_uri_page_renderer/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth import RedirectUriPageRenderer 4 | 5 | 6 | class TestInit(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_render_failure_page(self): 14 | renderer = RedirectUriPageRenderer( 15 | install_path="/slack/install", 16 | redirect_uri_path="/slack/oauth_redirect", 17 | ) 18 | page = renderer.render_failure_page("something-wrong") 19 | self.assertTrue("Something Went Wrong!" in page) 20 | 21 | def test_render_success_page(self): 22 | renderer = RedirectUriPageRenderer( 23 | install_path="/slack/install", 24 | redirect_uri_path="/slack/oauth_redirect", 25 | ) 26 | page = renderer.render_success_page(app_id="A111", team_id="T111") 27 | self.assertTrue("slack://app?team=T111&id=A111" in page) 28 | 29 | page = renderer.render_success_page(app_id="A111", team_id=None) 30 | self.assertTrue("slack://open" in page) 31 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/state_store/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/test_amazon_s3.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import boto3 4 | 5 | try: 6 | from moto import mock_aws 7 | except ImportError: 8 | from moto import mock_s3 as mock_aws 9 | from slack_sdk.oauth.state_store.amazon_s3 import AmazonS3OAuthStateStore 10 | 11 | 12 | class TestAmazonS3(unittest.TestCase): 13 | mock_aws = mock_aws() 14 | bucket_name = "test-bucket" 15 | 16 | def setUp(self): 17 | self.mock_aws.start() 18 | s3 = boto3.resource("s3") 19 | bucket = s3.Bucket(self.bucket_name) 20 | bucket.create(CreateBucketConfiguration={"LocationConstraint": "af-south-1"}) 21 | 22 | def tearDown(self): 23 | self.mock_aws.stop() 24 | 25 | def test_instance(self): 26 | store = AmazonS3OAuthStateStore( 27 | s3_client=boto3.client("s3"), 28 | bucket_name=self.bucket_name, 29 | expiration_seconds=10, 30 | ) 31 | self.assertIsNotNone(store) 32 | 33 | def test_issue_and_consume(self): 34 | store = AmazonS3OAuthStateStore( 35 | s3_client=boto3.client("s3"), 36 | bucket_name=self.bucket_name, 37 | expiration_seconds=10, 38 | ) 39 | state = store.issue() 40 | result = store.consume(state) 41 | self.assertTrue(result) 42 | result = store.consume(state) 43 | self.assertFalse(result) 44 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/test_async_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import unittest 3 | from tests.helpers import async_test 4 | from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine 5 | 6 | from slack_sdk.oauth.state_store.sqlalchemy import AsyncSQLAlchemyOAuthStateStore 7 | 8 | 9 | class TestSQLAlchemy(unittest.TestCase): 10 | engine: AsyncEngine 11 | 12 | @async_test 13 | async def setUp(self): 14 | self.engine = create_async_engine("sqlite+aiosqlite:///:memory:") 15 | self.store = AsyncSQLAlchemyOAuthStateStore(engine=self.engine, expiration_seconds=2) 16 | async with self.engine.begin() as conn: 17 | await conn.run_sync(self.store.metadata.create_all) 18 | 19 | @async_test 20 | async def tearDown(self): 21 | async with self.engine.begin() as conn: 22 | await conn.run_sync(self.store.metadata.drop_all) 23 | await self.engine.dispose() 24 | 25 | @async_test 26 | async def test_issue_and_consume(self): 27 | state = await self.store.async_issue() 28 | result = await self.store.async_consume(state) 29 | self.assertTrue(result) 30 | result = await self.store.async_consume(state) 31 | self.assertFalse(result) 32 | 33 | @async_test 34 | async def test_expiration(self): 35 | state = await self.store.async_issue() 36 | await asyncio.sleep(3) 37 | result = await self.store.async_consume(state) 38 | self.assertFalse(result) 39 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/test_file.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth.state_store import FileOAuthStateStore 4 | 5 | 6 | class TestFile(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_instance(self): 14 | store = FileOAuthStateStore(expiration_seconds=10) 15 | self.assertIsNotNone(store) 16 | 17 | def test_issue_and_consume(self): 18 | store = FileOAuthStateStore(expiration_seconds=10) 19 | state = store.issue() 20 | result = store.consume(state) 21 | self.assertTrue(result) 22 | result = store.consume(state) 23 | self.assertFalse(result) 24 | 25 | def test_kwargs(self): 26 | store = FileOAuthStateStore(expiration_seconds=10) 27 | store.issue(foo=123, bar="baz") 28 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/test_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import unittest 3 | 4 | import sqlalchemy 5 | from sqlalchemy.engine import Engine 6 | 7 | from slack_sdk.oauth.state_store.sqlalchemy import SQLAlchemyOAuthStateStore 8 | 9 | 10 | class TestSQLAlchemy(unittest.TestCase): 11 | engine: Engine 12 | 13 | def setUp(self): 14 | self.engine = sqlalchemy.create_engine("sqlite:///:memory:") 15 | self.store = SQLAlchemyOAuthStateStore(engine=self.engine, expiration_seconds=2) 16 | self.store.metadata.create_all(self.engine) 17 | 18 | def tearDown(self): 19 | self.store.metadata.drop_all(self.engine) 20 | self.engine.dispose() 21 | 22 | def test_issue_and_consume(self): 23 | state = self.store.issue() 24 | result = self.store.consume(state) 25 | self.assertTrue(result) 26 | result = self.store.consume(state) 27 | self.assertFalse(result) 28 | 29 | def test_expiration(self): 30 | state = self.store.issue() 31 | time.sleep(3) 32 | result = self.store.consume(state) 33 | self.assertFalse(result) 34 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_store/test_sqlite3.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth.state_store.sqlite3 import SQLite3OAuthStateStore 4 | 5 | 6 | class TestSQLite3(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_instance(self): 14 | store = SQLite3OAuthStateStore( 15 | database="logs/test.db", 16 | expiration_seconds=10, 17 | ) 18 | self.assertIsNotNone(store) 19 | 20 | def test_issue_and_consume(self): 21 | store = SQLite3OAuthStateStore( 22 | database="logs/test.db", 23 | expiration_seconds=10, 24 | ) 25 | state = store.issue() 26 | result = store.consume(state) 27 | self.assertTrue(result) 28 | result = store.consume(state) 29 | self.assertFalse(result) 30 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/state_utils/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/state_utils/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth import OAuthStateUtils 4 | 5 | 6 | class TestUtils(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_is_valid_browser(self): 14 | utils = OAuthStateUtils() 15 | cookie_name = OAuthStateUtils.default_cookie_name 16 | result = utils.is_valid_browser("state-value", {"cookie": f"{cookie_name}=state-value"}) 17 | self.assertTrue(result) 18 | result = utils.is_valid_browser("state-value", {"cookie": f"{cookie_name}=xxx"}) 19 | self.assertFalse(result) 20 | 21 | result = utils.is_valid_browser("state-value", {"cookie": [f"{cookie_name}=state-value"]}) 22 | self.assertTrue(result) 23 | result = utils.is_valid_browser("state-value", {"cookie": [f"{cookie_name}=xxx"]}) 24 | self.assertFalse(result) 25 | 26 | def test_build_set_cookie_for_new_state(self): 27 | utils = OAuthStateUtils() 28 | value = utils.build_set_cookie_for_new_state("state-value") 29 | expected = "slack-app-oauth-state=state-value; Secure; HttpOnly; Path=/; Max-Age=600" 30 | self.assertEqual(expected, value) 31 | 32 | def test_build_set_cookie_for_deletion(self): 33 | utils = OAuthStateUtils() 34 | value = utils.build_set_cookie_for_deletion() 35 | expected = "slack-app-oauth-state=deleted; Secure; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT" 36 | self.assertEqual(expected, value) 37 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.oauth import AuthorizeUrlGenerator 4 | 5 | 6 | class TestInit(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_generator(self): 14 | generator = AuthorizeUrlGenerator( 15 | client_id="111.222", 16 | scopes=["chat:write", "commands"], 17 | user_scopes=["search:read"], 18 | ) 19 | url = generator.generate("state-value") 20 | expected = "https://slack.com/oauth/v2/authorize?state=state-value&client_id=111.222&scope=chat:write,commands&user_scope=search:read" 21 | self.assertEqual(expected, url) 22 | -------------------------------------------------------------------------------- /tests/slack_sdk/oauth/token_rotation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/oauth/token_rotation/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/rtm_v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/rtm_v2/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/scim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/scim/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/scim/test_client_http_retry.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.http_retry import RateLimitErrorRetryHandler 4 | from slack_sdk.scim import SCIMClient 5 | from tests.slack_sdk.scim.mock_web_api_handler import MockHandler 6 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 7 | from ..my_retry_handler import MyRetryHandler 8 | 9 | 10 | class TestSCIMClient(unittest.TestCase): 11 | def setUp(self): 12 | setup_mock_web_api_server(self, MockHandler) 13 | 14 | def tearDown(self): 15 | cleanup_mock_web_api_server(self) 16 | 17 | def test_retries(self): 18 | retry_handler = MyRetryHandler(max_retry_count=2) 19 | client = SCIMClient( 20 | base_url="http://localhost:8888/", 21 | token="xoxp-remote_disconnected", 22 | retry_handlers=[retry_handler], 23 | ) 24 | 25 | try: 26 | client.search_users(start_index=0, count=1) 27 | self.fail("An exception is expected") 28 | except Exception as _: 29 | pass 30 | 31 | self.assertEqual(2, retry_handler.call_count) 32 | 33 | def test_ratelimited(self): 34 | client = SCIMClient( 35 | base_url="http://localhost:8888/", 36 | token="xoxp-ratelimited", 37 | ) 38 | client.retry_handlers.append(RateLimitErrorRetryHandler()) 39 | 40 | response = client.search_users(start_index=0, count=1) 41 | # Just running retries; no assertions for call count so far 42 | self.assertEqual(429, response.status_code) 43 | -------------------------------------------------------------------------------- /tests/slack_sdk/scim/test_internals.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from slack_sdk.scim.v1.internal_utils import _to_snake_cased 5 | 6 | 7 | class TEstInternals(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | def test_snake_cased(self): 15 | response_body = """{"totalResults":441,"itemsPerPage":1,"startIndex":1,"schemas":["urn:scim:schemas:core:1.0"],"Resources":[{"schemas":["urn:scim:schemas:core:1.0"],"id":"W111","externalId":"","meta":{"created":"2020-08-13T04:15:35-07:00","location":"https://api.slack.com/scim/v1/Users/W111"},"userName":"test-app","nickName":"test-app","name":{"givenName":"","familyName":""},"displayName":"","profileUrl":"https://test-test-test.enterprise.slack.com/team/test-app","title":"","timezone":"America/Los_Angeles","active":true,"emails":[{"value":"botuser@slack-bots.com","primary":true}],"photos":[{"value":"https://secure.gravatar.com/avatar/xxx.jpg","type":"photo"}],"groups":[]}]}""" 16 | result = _to_snake_cased(json.loads(response_body)) 17 | self.assertEqual(result["start_index"], 1) 18 | self.assertIsNotNone(result["resources"][0]["id"]) 19 | -------------------------------------------------------------------------------- /tests/slack_sdk/signature/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/signature/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/socket_mode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/socket_mode/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/socket_mode/logger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/socket_mode/logger/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/socket_mode/test_request.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from slack_sdk.socket_mode.request import SocketModeRequest 5 | 6 | 7 | class TestRequest(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | def test(self): 15 | body = json.loads( 16 | """{"envelope_id":"1d3c79ab-0ffb-41f3-a080-d19e85f53649","payload":{"token":"xxx","team_id":"T111","team_domain":"xxx","channel_id":"C03E94MKU","channel_name":"random","user_id":"U111","user_name":"seratch","command":"/hello-socket-mode","text":"","api_app_id":"A111","response_url":"https://hooks.slack.com/commands/T111/111/xxx","trigger_id":"111.222.xxx"},"type":"slash_commands","accepts_response_payload":true}""" 17 | ) 18 | req = SocketModeRequest.from_dict(body) 19 | self.assertIsNotNone(req) 20 | self.assertEqual(req.envelope_id, "1d3c79ab-0ffb-41f3-a080-d19e85f53649") 21 | -------------------------------------------------------------------------------- /tests/slack_sdk/socket_mode/test_response.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from slack_sdk.socket_mode.response import SocketModeResponse 5 | 6 | 7 | class TestResponse(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | def test_parser(self): 15 | text = """{"envelope_id":"1d3c79ab-0ffb-41f3-a080-d19e85f53649","payload":{"text":"Thanks!"}}""" 16 | body = json.loads(text) 17 | response = SocketModeResponse(body["envelope_id"], body["payload"]) 18 | self.assertIsNotNone(response) 19 | self.assertEqual(response.envelope_id, "1d3c79ab-0ffb-41f3-a080-d19e85f53649") 20 | self.assertEqual(response.payload.get("text"), "Thanks!") 21 | 22 | def test_to_dict(self): 23 | response = SocketModeResponse(envelope_id="xxx", payload={"text": "hi"}) 24 | self.assertDictEqual(response.to_dict(), {"envelope_id": "xxx", "payload": {"text": "hi"}}) 25 | -------------------------------------------------------------------------------- /tests/slack_sdk/socket_mode/test_websocket_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.socket_mode.websocket_client import SocketModeClient 4 | 5 | 6 | class TestWebSocketClientLibrary(unittest.TestCase): 7 | def setUp(self): 8 | pass 9 | 10 | def tearDown(self): 11 | pass 12 | 13 | def test_init_close(self): 14 | client = SocketModeClient(app_token="xapp-A111-222-xyz") 15 | try: 16 | self.assertIsNotNone(client) 17 | self.assertFalse(client.is_connected()) 18 | finally: 19 | client.close() 20 | -------------------------------------------------------------------------------- /tests/slack_sdk/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk/web/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk/web/test_web_client_http_retry_connection.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.web import WebClient 4 | from tests.slack_sdk.web.mock_web_api_http_retry_handler import MockHandler 5 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 6 | 7 | 8 | class TestWebClient_HttpRetry(unittest.TestCase): 9 | def setUp(self): 10 | setup_mock_web_api_server(self, MockHandler, port=8889) 11 | 12 | def tearDown(self): 13 | cleanup_mock_web_api_server(self) 14 | 15 | def test_remote_disconnected(self): 16 | client = WebClient( 17 | base_url="http://localhost:8889", 18 | token="xoxb-remote_disconnected", 19 | team_id="T111", 20 | ) 21 | client.auth_test() 22 | -------------------------------------------------------------------------------- /tests/slack_sdk/web/test_web_client_issue_1049.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.web import WebClient 4 | from tests.slack_sdk.web.mock_web_api_handler import MockHandler 5 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 6 | 7 | 8 | class TestWebClient_Issue_1049(unittest.TestCase): 9 | def setUp(self): 10 | setup_mock_web_api_server(self, MockHandler) 11 | 12 | def tearDown(self): 13 | cleanup_mock_web_api_server(self) 14 | 15 | def test_the_pattern(self): 16 | client = WebClient( 17 | base_url="http://localhost:8888", 18 | token="xoxb-admin_convo_pagination", 19 | ) 20 | pages = [] 21 | for page in client.admin_conversations_search(query="announcement"): 22 | pages.append(page) 23 | self.assertEqual(len(pages), 2) 24 | -------------------------------------------------------------------------------- /tests/slack_sdk/web/test_web_client_issue_829.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import slack_sdk.errors as err 4 | from slack_sdk.web import WebClient 5 | from tests.slack_sdk.web.mock_web_api_handler import MockHandler 6 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 7 | 8 | 9 | class TestWebClient_Issue_829(unittest.TestCase): 10 | def setUp(self): 11 | setup_mock_web_api_server(self, MockHandler) 12 | 13 | def tearDown(self): 14 | cleanup_mock_web_api_server(self) 15 | 16 | def test_html_response_body_issue_829(self): 17 | client = WebClient(base_url="http://localhost:8888") 18 | try: 19 | client.users_list(token="xoxb-error_html_response") 20 | self.fail("SlackApiError expected here") 21 | except err.SlackApiError as e: 22 | self.assertTrue( 23 | str(e).startswith("The request to the Slack API failed. (url: http://"), 24 | e, 25 | ) 26 | self.assertIsInstance(e.response.status_code, int) 27 | self.assertFalse(e.response["ok"]) 28 | self.assertTrue( 29 | e.response["error"].startswith("Received a response in a non-JSON format: bool: 29 | self.call_count += 1 30 | return response is not None and response.status_code == 200 and response.body.get("error") == "fatal_error" 31 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/helpers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | def async_test(coro): 5 | loop = asyncio.new_event_loop() 6 | asyncio.set_event_loop(loop) 7 | 8 | def wrapper(*args, **kwargs): 9 | future = coro(*args, **kwargs) 10 | return asyncio.get_event_loop().run_until_complete(future) 11 | 12 | return wrapper 13 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/http_retry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/http_retry/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/http_retry/test_builtins.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers 4 | 5 | 6 | class TestBuiltins(unittest.TestCase): 7 | def test_default_ones(self): 8 | list = async_default_handlers() 9 | self.assertEqual(1, len(list)) 10 | list.clear() 11 | self.assertEqual(0, len(list)) 12 | list = async_default_handlers() 13 | self.assertEqual(1, len(list)) 14 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/my_retry_handler.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from aiohttp import ServerDisconnectedError, ClientOSError 3 | 4 | from slack_sdk.http_retry.async_handler import AsyncRetryHandler 5 | from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator 6 | from slack_sdk.http_retry.state import RetryState 7 | from slack_sdk.http_retry.request import HttpRequest 8 | from slack_sdk.http_retry.response import HttpResponse 9 | from slack_sdk.http_retry.handler import default_interval_calculator 10 | 11 | 12 | class MyRetryHandler(AsyncRetryHandler): 13 | def __init__( 14 | self, 15 | max_retry_count: int = 1, 16 | interval_calculator: RetryIntervalCalculator = default_interval_calculator, 17 | ): 18 | super().__init__(max_retry_count, interval_calculator) 19 | self.call_count = 0 20 | 21 | async def _can_retry_async( 22 | self, 23 | *, 24 | state: RetryState, 25 | request: HttpRequest, 26 | response: Optional[HttpResponse], 27 | error: Optional[Exception], 28 | ) -> bool: 29 | self.call_count += 1 30 | if error is None: 31 | return False 32 | for error_type in [ServerDisconnectedError, ClientOSError]: 33 | if isinstance(error, error_type): 34 | return True 35 | return False 36 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/oauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/oauth/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/oauth/installation_store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/oauth/installation_store/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/oauth/token_rotation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/oauth/token_rotation/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/scim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/scim/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/socket_mode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/socket_mode/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/web/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_async/web/test_async_slack_response.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.web.async_slack_response import AsyncSlackResponse 4 | from slack_sdk.web.async_client import AsyncWebClient 5 | 6 | 7 | class TestAsyncSlackResponse(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | # https://github.com/slackapi/python-slack-sdk/issues/1100 15 | def test_issue_1100(self): 16 | response = AsyncSlackResponse( 17 | client=AsyncWebClient(token="xoxb-dummy"), 18 | http_verb="POST", 19 | api_url="http://localhost:3000/api.test", 20 | req_args={}, 21 | data=None, 22 | headers={}, 23 | status_code=200, 24 | ) 25 | with self.assertRaises(ValueError): 26 | response["foo"] 27 | 28 | foo = response.get("foo") 29 | self.assertIsNone(foo) 30 | 31 | # https://github.com/slackapi/python-slack-sdk/issues/1102 32 | def test_issue_1102(self): 33 | response = AsyncSlackResponse( 34 | client=AsyncWebClient(token="xoxb-dummy"), 35 | http_verb="POST", 36 | api_url="http://localhost:3000/api.test", 37 | req_args={}, 38 | data={"ok": True, "args": {"hello": "world"}}, 39 | headers={}, 40 | status_code=200, 41 | ) 42 | self.assertTrue("ok" in response) 43 | self.assertTrue("foo" not in response) 44 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/web/test_web_client_issue_829.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import slack_sdk.errors as err 4 | from slack_sdk.web.async_client import AsyncWebClient 5 | from tests.helpers import async_test 6 | from tests.slack_sdk.web.mock_web_api_handler import MockHandler 7 | from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async 8 | 9 | 10 | class TestWebClient_Issue_829(unittest.TestCase): 11 | def setUp(self): 12 | setup_mock_web_api_server_async(self, MockHandler) 13 | 14 | def tearDown(self): 15 | cleanup_mock_web_api_server_async(self) 16 | 17 | @async_test 18 | async def test_html_response_body_issue_829_async(self): 19 | client = AsyncWebClient(base_url="http://localhost:8888") 20 | try: 21 | await client.users_list(token="xoxb-error_html_response") 22 | self.fail("SlackApiError expected here") 23 | except err.SlackApiError as e: 24 | self.assertEqual( 25 | "The request to the Slack API failed. (url: http://localhost:8888/users.list, status: 503)\n" 26 | "The server responded with: {}", 27 | str(e), 28 | ) 29 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/web/test_web_client_issue_921_custom_logger.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from logging import Logger 3 | 4 | from slack_sdk.web.async_client import AsyncWebClient 5 | from tests.helpers import async_test 6 | from tests.slack_sdk.web.mock_web_api_handler import MockHandler 7 | from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async 8 | 9 | 10 | class TestWebClient_Issue_921_CustomLogger(unittest.TestCase): 11 | def setUp(self): 12 | setup_mock_web_api_server_async(self, MockHandler) 13 | 14 | def tearDown(self): 15 | cleanup_mock_web_api_server_async(self) 16 | 17 | @async_test 18 | async def test_if_it_uses_custom_logger(self): 19 | logger = CustomLogger("test-logger") 20 | client = AsyncWebClient( 21 | base_url="http://localhost:8888", 22 | token="xoxb-api_test", 23 | logger=logger, 24 | ) 25 | await client.chat_postMessage(channel="C111", text="hello") 26 | self.assertTrue(logger.called) 27 | 28 | 29 | class CustomLogger(Logger): 30 | called: bool 31 | 32 | def __init__(self, name, level="DEBUG"): 33 | Logger.__init__(self, name, level) 34 | self.called = False 35 | 36 | def debug(self, msg, *args, **kwargs): 37 | self.called = True 38 | -------------------------------------------------------------------------------- /tests/slack_sdk_async/webhook/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_async/webhook/__init__.py -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/channel.created.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "channel_created", 3 | "channel": { 4 | "id": "C024BE91L", 5 | "name": "fun", 6 | "created": 1360782804, 7 | "creator": "U024BE7LH" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/im.created.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "im_created", 3 | "user": "U024BE7LH", 4 | "channel": { 5 | "id": "D024BE91L", 6 | "user": "U123BL234", 7 | "created": 1360782804 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/slack_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_fixture/slack_logo.png -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/slack_logo_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tests/slack_sdk_fixture/slack_logo_new.png -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/view_home_006.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VMHU10V25", 3 | "team_id": "T8N4K1JN", 4 | "type": "home", 5 | "blocks": [ 6 | { 7 | "type": "section", 8 | "block_id": "2WGp9", 9 | "text": { 10 | "type": "mrkdwn", 11 | "text": "A simple section with some sample sentence.", 12 | "verbatim": false 13 | } 14 | } 15 | ], 16 | "private_metadata": "Shh it is a secret", 17 | "callback_id": "identify_your_home_tab", 18 | "hash": "156772938.1827394", 19 | "clear_on_close": false, 20 | "notify_on_close": false, 21 | "root_view_id": "VMHU10V25", 22 | "app_id": "AA4928AQ", 23 | "external_id": "some-unique-id", 24 | "bot_id": "BA13894H" 25 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/view_modal_008.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "modal", 3 | "title": { 4 | "type": "plain_text", 5 | "text": "My App", 6 | "emoji": true 7 | }, 8 | "submit": { 9 | "type": "plain_text", 10 | "text": "Submit", 11 | "emoji": true 12 | }, 13 | "close": { 14 | "type": "plain_text", 15 | "text": "Cancel", 16 | "emoji": true 17 | }, 18 | "private_metadata": "something important here", 19 | "blocks": [ 20 | { 21 | "type": "section", 22 | "text": { 23 | "type": "mrkdwn", 24 | "text": "This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and " 25 | } 26 | } 27 | ], 28 | "state": { 29 | "values": { 30 | "multi-line": { 31 | "ml-value": { 32 | "type": "plain_text_input", 33 | "value": "This is my example inputted value" 34 | } 35 | } 36 | } 37 | }, 38 | "hash": "156663117.cd33ad1f" 39 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/view_modal_009.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VNM522E2U", 3 | "team_id": "T9M4RL1JM", 4 | "type": "modal", 5 | "title": { 6 | "type": "plain_text", 7 | "text": "Pushed Modal", 8 | "emoji": true 9 | }, 10 | "close": { 11 | "type": "plain_text", 12 | "text": "Back", 13 | "emoji": true 14 | }, 15 | "submit": { 16 | "type": "plain_text", 17 | "text": "Save", 18 | "emoji": true 19 | }, 20 | "blocks": [ 21 | { 22 | "type": "input", 23 | "block_id": "edit_details", 24 | "element": { 25 | "type": "plain_text_input", 26 | "action_id": "detail_input" 27 | }, 28 | "label": { 29 | "type": "plain_text", 30 | "text": "Edit details" 31 | } 32 | } 33 | ], 34 | "private_metadata": "secret", 35 | "callback_id": "view_4", 36 | "external_id": "some-unique-id", 37 | "state": { 38 | "values": {} 39 | }, 40 | "hash": "1569362015.55b5e41b", 41 | "clear_on_close": true, 42 | "notify_on_close": false, 43 | "root_view_id": "VNN729E3U", 44 | "app_id": "AAD3351BQ", 45 | "bot_id": "BADF7A34H" 46 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/view_modal_010.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VMHU10V25", 3 | "team_id": "T8N4K1JN", 4 | "type": "modal", 5 | "title": { 6 | "type": "plain_text", 7 | "text": "Quite a plain modal" 8 | }, 9 | "submit": { 10 | "type": "plain_text", 11 | "text": "Create" 12 | }, 13 | "blocks": [ 14 | { 15 | "type": "input", 16 | "block_id": "a_block_id", 17 | "label": { 18 | "type": "plain_text", 19 | "text": "A simple label", 20 | "emoji": true 21 | }, 22 | "optional": false, 23 | "element": { 24 | "type": "plain_text_input", 25 | "action_id": "an_action_id" 26 | } 27 | } 28 | ], 29 | "private_metadata": "Shh it is a secret", 30 | "callback_id": "identify_your_modals", 31 | "external_id": "some-unique-id", 32 | "state": { 33 | "values": {} 34 | }, 35 | "hash": "156772938.1827394", 36 | "clear_on_close": false, 37 | "notify_on_close": false, 38 | "root_view_id": "VMHU10V25", 39 | "app_id": "AA4928AQ", 40 | "bot_id": "BA13894H" 41 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_admin_convo_pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "conversations": [ 4 | { 5 | "id": "C013T0FTKU3", 6 | "name": "random", 7 | "purpose": "test", 8 | "member_count": 11, 9 | "created": 1589700571, 10 | "creator_id": "W111", 11 | "is_private": false, 12 | "is_archived": false, 13 | "is_general": false, 14 | "last_activity_ts": 1599130434000900, 15 | "is_ext_shared": false, 16 | "is_global_shared": false, 17 | "is_org_default": false, 18 | "is_org_mandatory": false, 19 | "is_org_shared": false, 20 | "is_frozen": false, 21 | "internal_team_ids_count": 1, 22 | "internal_team_ids_sample_team": "T111", 23 | "pending_connected_team_ids": [], 24 | "is_pending_ext_shared": false 25 | } 26 | ], 27 | "next_cursor": "1" 28 | } 29 | -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_admin_convo_pagination_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "conversations": [], 4 | "next_cursor": "" 5 | } 6 | -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_api_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_api_test_false.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": false 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_conversations_list_pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C1" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "has_page2" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_conversations_list_pagination2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C2" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "page2" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_conversations_list_pagination2_page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C3" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_conversations_list_pagination_has_page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C2" 6 | } 7 | ], 8 | "response_metadata": { 9 | "next_cursor": "has_page3" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_conversations_list_pagination_has_page3.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "channels": [ 4 | { 5 | "id": "C3" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_fatal_error_only_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_rate_limited_only_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_ratelimited_only_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_users_list_pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "members": [ 4 | "Bob", 5 | "cat" 6 | ], 7 | "response_metadata": { 8 | "next_cursor": 1 9 | } 10 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_users_list_pagination_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "members": [ 4 | "Kevin", 5 | "dog" 6 | ], 7 | "response_metadata": { 8 | "next_cursor": "" 9 | } 10 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/web_response_users_setPhoto.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true 3 | } -------------------------------------------------------------------------------- /tests/slack_sdk_fixture/日本語.txt: -------------------------------------------------------------------------------- 1 | 日本語の文書です。 -------------------------------------------------------------------------------- /tests/test_aiohttp_version_checker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | from slack_sdk.aiohttp_version_checker import validate_aiohttp_version 5 | 6 | 7 | class TestAiohttpVersionChecker(unittest.TestCase): 8 | def setUp(self): 9 | self.logger = logging.getLogger(__name__) 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | def test_not_recommended_versions(self): 15 | state = {"counter": 0} 16 | 17 | def print(message: str): 18 | state["counter"] = state["counter"] + 1 19 | 20 | validate_aiohttp_version("2.1.3", print) 21 | self.assertEqual(state["counter"], 1) 22 | validate_aiohttp_version("3.6.3", print) 23 | self.assertEqual(state["counter"], 2) 24 | validate_aiohttp_version("3.7.0", print) 25 | self.assertEqual(state["counter"], 3) 26 | 27 | def test_recommended_versions(self): 28 | state = {"counter": 0} 29 | 30 | def print(message: str): 31 | state["counter"] = state["counter"] + 1 32 | 33 | validate_aiohttp_version("3.7.1", print) 34 | self.assertEqual(state["counter"], 0) 35 | validate_aiohttp_version("3.7.3", print) 36 | self.assertEqual(state["counter"], 0) 37 | validate_aiohttp_version("3.8.0", print) 38 | self.assertEqual(state["counter"], 0) 39 | validate_aiohttp_version("4.0.0", print) 40 | self.assertEqual(state["counter"], 0) 41 | validate_aiohttp_version("4.0.0rc1", print) 42 | self.assertEqual(state["counter"], 0) 43 | -------------------------------------------------------------------------------- /tests/test_proxy_env_variable_loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env 5 | from tests.helpers import remove_os_env_temporarily, restore_os_env 6 | 7 | 8 | class TestProxyEnvVariableLoader(unittest.TestCase): 9 | def setUp(self): 10 | self.old_env = remove_os_env_temporarily() 11 | 12 | def tearDown(self): 13 | os.environ.clear() 14 | restore_os_env(self.old_env) 15 | 16 | def test_load_lower_case(self): 17 | os.environ["https_proxy"] = "http://localhost:9999" 18 | url = load_http_proxy_from_env() 19 | self.assertEqual(url, "http://localhost:9999") 20 | 21 | def test_load_upper_case(self): 22 | os.environ["HTTPS_PROXY"] = "http://localhost:9999" 23 | url = load_http_proxy_from_env() 24 | self.assertEqual(url, "http://localhost:9999") 25 | 26 | def test_load_all_empty_case(self): 27 | os.environ["HTTP_PROXY"] = "" 28 | os.environ["http_proxy"] = "" 29 | os.environ["HTTPS_PROXY"] = "" 30 | os.environ["https_proxy"] = "" 31 | url = load_http_proxy_from_env() 32 | self.assertEqual(url, None) 33 | 34 | def test_proxy_url_is_none_case(self): 35 | os.environ.pop("HTTPS_PROXY", None) 36 | os.environ.pop("https_proxy", None) 37 | os.environ.pop("HTTP_PROXY", None) 38 | os.environ.pop("http_proxy", None) 39 | url = load_http_proxy_from_env() 40 | self.assertEqual(url, None) 41 | -------------------------------------------------------------------------------- /tests/web/classes/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack.web.classes import extract_json 4 | from slack.web.classes.objects import PlainTextObject, MarkdownTextObject 5 | 6 | 7 | class TestInit(unittest.TestCase): 8 | def test_from_list_of_json_objects(self): 9 | json_objects = [ 10 | PlainTextObject.from_str("foo"), 11 | MarkdownTextObject.from_str("bar"), 12 | ] 13 | output = extract_json(json_objects) 14 | expected = { 15 | "result": [ 16 | {"type": "plain_text", "text": "foo", "emoji": True}, 17 | {"type": "mrkdwn", "text": "bar"}, 18 | ] 19 | } 20 | self.assertDictEqual(expected, {"result": output}) 21 | 22 | def test_from_single_json_object(self): 23 | single_json_object = PlainTextObject.from_str("foo") 24 | output = extract_json(single_json_object) 25 | expected = {"result": {"type": "plain_text", "text": "foo", "emoji": True}} 26 | self.assertDictEqual(expected, {"result": output}) 27 | -------------------------------------------------------------------------------- /tests/web/classes/test_messages.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack_sdk.models.messages.message import Message 4 | 5 | 6 | class MessageTests(unittest.TestCase): 7 | def test_validate_json_fails(self): 8 | msg = Message(text="Hi there") 9 | self.assertIsNotNone(msg) 10 | -------------------------------------------------------------------------------- /tests/web/test_slack_response.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from slack import WebClient 4 | from slack.web.slack_response import SlackResponse 5 | 6 | 7 | class TestSlackResponse(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def tearDown(self): 12 | pass 13 | 14 | # https://github.com/slackapi/python-slackclient/issues/559 15 | def test_issue_559(self): 16 | response = SlackResponse( 17 | client=WebClient(token="xoxb-dummy"), 18 | http_verb="POST", 19 | api_url="http://localhost:3000/api.test", 20 | req_args={}, 21 | data={"ok": True, "args": {"hello": "world"}}, 22 | headers={}, 23 | status_code=200, 24 | ) 25 | 26 | self.assertTrue("ok" in response.data) 27 | self.assertTrue("args" in response.data) 28 | self.assertFalse("error" in response.data) 29 | -------------------------------------------------------------------------------- /tests/web/test_web_client_coverage.py: -------------------------------------------------------------------------------- 1 | # We no longer maintain this test. 2 | # Add new method tests to slack_sdk_tests_async/web/test_web_client_coverage.py 3 | -------------------------------------------------------------------------------- /tests/web/test_web_client_functional.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import slack 4 | from tests.web.mock_web_api_handler import MockHandler 5 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 6 | 7 | 8 | class TestWebClientFunctional(unittest.TestCase): 9 | def setUp(self): 10 | setup_mock_web_api_server(self, MockHandler) 11 | self.client = slack.WebClient(token="xoxb-api_test", base_url="http://localhost:8888") 12 | 13 | def tearDown(self): 14 | cleanup_mock_web_api_server(self) 15 | 16 | def test_requests_with_use_session_turned_off(self): 17 | self.client.use_pooling = False 18 | resp = self.client.api_test() 19 | assert resp["ok"] 20 | 21 | def test_subsequent_requests_with_a_session_succeeds(self): 22 | resp = self.client.api_test() 23 | assert resp["ok"] 24 | resp = self.client.api_test() 25 | assert resp["ok"] 26 | -------------------------------------------------------------------------------- /tests/web/test_web_client_issue_921_custom_logger.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from logging import Logger 3 | 4 | from slack.web import WebClient 5 | from tests.slack_sdk.web.mock_web_api_handler import MockHandler 6 | from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server 7 | 8 | 9 | class TestWebClient_Issue_921_CustomLogger(unittest.TestCase): 10 | def setUp(self): 11 | setup_mock_web_api_server(self, MockHandler) 12 | 13 | def tearDown(self): 14 | cleanup_mock_web_api_server(self) 15 | 16 | def test_if_it_uses_custom_logger(self): 17 | logger = CustomLogger("test-logger") 18 | client = WebClient( 19 | base_url="http://localhost:8888", 20 | token="xoxb-api_test", 21 | logger=logger, 22 | ) 23 | client.chat_postMessage(channel="C111", text="hello") 24 | self.assertTrue(logger.called) 25 | 26 | 27 | class CustomLogger(Logger): 28 | called: bool 29 | 30 | def __init__(self, name, level="DEBUG"): 31 | Logger.__init__(self, name, level) 32 | self.called = False 33 | 34 | def debug(self, msg, *args, **kwargs): 35 | self.called = True 36 | -------------------------------------------------------------------------------- /tutorial/PythOnBoardingBot/requirements.txt: -------------------------------------------------------------------------------- 1 | slack_sdk>=3.0 2 | slack_bolt>=1.6.1 3 | certifi -------------------------------------------------------------------------------- /tutorial/assets/authorize-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/authorize-install.png -------------------------------------------------------------------------------- /tutorial/assets/bot-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/bot-token.png -------------------------------------------------------------------------------- /tutorial/assets/enable-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/enable-events.png -------------------------------------------------------------------------------- /tutorial/assets/oauth-installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/oauth-installation.png -------------------------------------------------------------------------------- /tutorial/assets/oauth-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/oauth-permissions.png -------------------------------------------------------------------------------- /tutorial/assets/signing-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/signing-secret.png -------------------------------------------------------------------------------- /tutorial/assets/subscribe-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slackapi/python-slack-sdk/443ca03e425d93f47822a4de5776ff0595f5ed3d/tutorial/assets/subscribe-events.png --------------------------------------------------------------------------------