slack_sdk 3.0.0 was released on November 9, 2020. Slack's docs describe slackclient as in maintenance mode with slack_sdk as its successor, and the last slackclient release on PyPI, 2.9.4, tells you to use slack-sdk instead. Slack lists additional features in v3, including Socket Mode, OAuth flow support, the SCIM and Audit Logs APIs and retry handlers. Slack lists only four changes from 2.x and says existing code can run on v3 unchanged through a deprecated slack compatibility package. That package is useful for a staged rollout, but it maps slack.WebClient to a legacy class, so the import rename is where behavior actually changes. This guide follows Slack's v3 migration guide and SDK docs, linked at the end.
What changed
- The PyPI project is
slack_sdk(slack-sdkalso works). Removeslackclientfrom your requirements. - Import from
slack_sdk, for examplefrom slack_sdk import WebClient.slack.*imports still work but emit a deprecationUserWarning. slack_sdkhas no required dependencies, soaiohttpis no longer installed for you.WebClientis synchronous only.run_async=Trueand other aiohttp options are gone; useAsyncWebClient, orLegacyWebClientfor the v2 behavior.- Errors live in
slack_sdk.errors, signature verification inslack_sdk.signature, and theslack.web.classesmodels inslack_sdk.models. - For the legacy RTM API there is a new
slack_sdk.rtm_v2.RTMClientnext to the v2-styleslack_sdk.rtm.RTMClient.
Migration steps
Swap the package
pip uninstall slackclient pip install slack_sdkChange
requirements.txtor your lock file in the same commit. If you useAsyncWebClient,AsyncWebhookClientorLegacyWebClient, addaiohttpexplicitly:slackclientpulled it in,slack_sdkdoes not. The v2-style RTM client inslack_sdk.rtmimports aiohttp as well. The current slack_sdk release declares Python 3.7 or later.Rename the imports
Before
from slack import WebClient from slack.errors import SlackApiError from slack.signature import SignatureVerifier from slack.web.classes.blocks import SectionBlockAfter
from slack_sdk import WebClient from slack_sdk.errors import SlackApiError from slack_sdk.signature import SignatureVerifier from slack_sdk.models.blocks import SectionBlockMethod calls do not change:
client.chat_postMessage(channel=..., text=...)returns the same response object, and a failed call still raisesSlackApiErrorwith the error code ine.response["error"]. Slack's guide suggests asedreplacement offrom slackwithfrom slack_sdkfor most files, and recommends changingslack.web.classes.*imports by hand, because those models moved toslack_sdk.modelswith a different layout. Webhooks follow the same pattern:from slack_sdk.webhook import WebhookClient.Move run_async code to AsyncWebClient
Before
from slack import WebClient from slack.errors import SlackApiError client = WebClient(token=os.environ["SLACK_API_TOKEN"], run_async=True) async def send_to_slack(channel, text): try: response = await client.chat_postMessage(channel=channel, text=text) except SlackApiError as e: log.warning("Slack error: %s", e.response["error"])After
# requires: pip install aiohttp from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.errors import SlackApiError client = AsyncWebClient(token=os.environ["SLACK_API_TOKEN"]) async def send_to_slack(channel, text): try: response = await client.chat_postMessage(channel=channel, text=text) except SlackApiError as e: log.warning("Slack error: %s", e.response["error"])Slack recommends
AsyncWebClientfor anyone usingrun_async=True: it is a plain asyncio client, so every call is a coroutine youawait. Code that drove the v2 client withloop.run_until_complete(future)can callasyncio.run(...)on an async function instead.Or keep v2 behavior with LegacyWebClient
If you cannot rewrite async call sites yet, import the v2-compatible class under its own name. Slack says that apart from the name there is no breaking change in it, and it needs
aiohttpinstalled:from slack_sdk.web.legacy_client import LegacyWebClient client = LegacyWebClient(token=os.environ["SLACK_API_TOKEN"], run_async=True)This removes the deprecation warning without changing behavior. Treat it as a stopping point rather than a destination; the synchronous
WebClientandAsyncWebClientare where Slack's docs and new features are.Update RTM clients
Before
import slack @slack.RTMClient.run_on(event="message") def say_hello(**payload): data = payload["data"] web_client = payload["web_client"] if "Hello" in data["text"]: web_client.chat_postMessage(channel=data["channel"], text="Hi!", thread_ts=data["ts"]) slack.RTMClient(token=os.environ["SLACK_BOT_TOKEN"]).start()After
from slack_sdk.rtm_v2 import RTMClient rtm = RTMClient(token=os.environ["SLACK_BOT_TOKEN"]) @rtm.on("message") def say_hello(client: RTMClient, event: dict): if "Hello" in event["text"]: client.web_client.chat_postMessage(channel=event["channel"], text="Hi!", thread_ts=event["ts"]) rtm.start()slack_sdk.rtm.RTMClientkeeps the v2run_ondecorator API if you need a smaller diff, but Slack does not recommend it for new development because it has issues thatrtm_v2fixes. Note thatrtm_v2does not support thertm.startAPI method. The legacy RTM API also requires a classic Slack app with a plainbotscope; for new work Slack recommends the HTTP-based Events API.
How to find every affected call
Find the risky constructors before renaming anything, then every remaining slack import. ast-grep matches the syntax tree, so multi-line calls are found too:
sg run --pattern 'WebClient($$$BEFORE, run_async=$VALUE, $$$AFTER)' --lang python
sg run --pattern 'slack.WebClient($$$BEFORE, run_async=$VALUE, $$$AFTER)' --lang python
sg run --pattern 'from slack import $$$NAMES' --lang python
sg run --pattern 'import slack' --lang python
sg run --pattern 'from slack.errors import $$$NAMES' --lang python
sg run --pattern 'slack.RTMClient($$$ARGS)' --lang pythonImports from submodules such as slack.web.classes.blocks or slack.signature need one search each, or a plain text search for from slack.. If a file does import slack as $ALIAS, repeat the constructor searches with that alias. On Linux, sg may be a system command; ast-grep run is the same tool.
Testing the change
- Check that
slackclientis gone from the installed environment and lock file, not only fromrequirements.txt;pip show slackclientshould find nothing. - Run the test suite once with
python -W error::UserWarning -m pytest. Any remainingslackimport raises its deprecation warning as an error and points at the file. Slack's compatibility package honorsSLACKCLIENT_SKIP_DEPRECATIONto silence the warning, so make sure it is not set in that run. - Fix mocks. Patches on
slack.WebClient.chat_postMessagedo not interceptslack_sdk.WebClient; patch the class your code now imports, or inject a fake client. - Import every module in the deployed image, not just in development. A missing
aiohttponly shows up as anImportErrorwhere the async client or RTM module is loaded.
Automating it
Slack does not ship a codemod. Its migration guide suggests a sed one-liner for most from slack imports and manual edits for slack.web.classes. As step 2 explains, a blind rename is also exactly how run_async code breaks.
Versyka finds the call sites deterministically with ast-grep, rewrites them along with the code around them (async clients, RTM handlers, mocks, requirements), then runs your repository's own tests in an isolated virtualenv inside a disposable sandbox. It opens a pull request only if they pass.
Was this guide helpful?
Check your own repository
Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.
Sources
- Slack: Migrating from slackclient (v2.x to v3.x)
- Slack: Web client (python-slack-sdk)
- Slack: RTM API client (python-slack-sdk)
- Slack: python-slack-sdk v3.0.0 release
- Slack: slackclient v2.9.3 README (async usage)
- Slack: slack compatibility package source
- PyPI: slackclient (maintenance mode)
- ast-grep: pattern syntax