|
| 1 | +# AGENTS.md — Python Slack SDK |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +The Python Slack SDK (`slack_sdk`) is a modular Python library for interacting with the Slack platform APIs. It is published on PyPI as `slack-sdk`. The SDK provides independent packages for each Slack API surface: Web API, Webhooks, Socket Mode, OAuth, Audit Logs, SCIM, RTM, Block Kit models, and request signature verification. |
| 6 | + |
| 7 | +- **Repository**: <https://github.com/slackapi/python-slack-sdk> |
| 8 | +- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/> |
| 9 | +- **PyPI**: <https://pypi.org/project/slack-sdk/> |
| 10 | +- **Current version**: defined in `slack_sdk/version.py` |
| 11 | + |
| 12 | +## Critical Rules |
| 13 | + |
| 14 | +These are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code: |
| 15 | + |
| 16 | +1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly: |
| 17 | + - `slack_sdk/web/async_client.py` |
| 18 | + - `slack_sdk/web/legacy_client.py` |
| 19 | + - `slack_sdk/web/async_chat_stream.py` |
| 20 | + |
| 21 | + Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)). |
| 22 | + |
| 23 | +2. **Zero runtime dependencies.** The core sync Web API client must have no required runtime dependencies. Do not add entries to `install_requires` / `dependencies` in `pyproject.toml`. |
| 24 | + |
| 25 | +3. **Do not modify the legacy `slack/` package.** It is in maintenance mode and only re-exports from `slack_sdk` with deprecation warnings. All new development goes in `slack_sdk/`. |
| 26 | + |
| 27 | +4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:** |
| 28 | + |
| 29 | + ```sh |
| 30 | + python scripts/codegen.py --path . |
| 31 | + ./scripts/format.sh |
| 32 | + ``` |
| 33 | + |
| 34 | +5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting. |
| 35 | + |
| 36 | +## Architecture |
| 37 | + |
| 38 | +### Package Structure |
| 39 | + |
| 40 | +```text |
| 41 | +slack_sdk/ # Main package (active development) |
| 42 | +├── web/ # Web API client (sync, async, legacy) |
| 43 | +│ ├── client.py # *** CANONICAL SOURCE — edit this file *** |
| 44 | +│ ├── async_client.py # AUTO-GENERATED from client.py (do not edit) |
| 45 | +│ ├── legacy_client.py # AUTO-GENERATED from client.py (do not edit) |
| 46 | +│ ├── base_client.py # Sync HTTP transport (urllib) |
| 47 | +│ ├── async_base_client.py # Async HTTP transport (aiohttp) |
| 48 | +│ ├── legacy_base_client.py |
| 49 | +│ ├── slack_response.py # Response wrapper (sync) |
| 50 | +│ ├── async_slack_response.py |
| 51 | +│ ├── chat_stream.py # Streaming chat (edit this file) |
| 52 | +│ ├── async_chat_stream.py # AUTO-GENERATED from chat_stream.py (do not edit) |
| 53 | +│ └── internal_utils.py # Shared helpers |
| 54 | +├── webhook/ # Incoming Webhooks & response_url |
| 55 | +├── socket_mode/ # Socket Mode (multiple backend implementations) |
| 56 | +│ ├── builtin/ # Built-in WebSocket (no extra deps) |
| 57 | +│ ├── aiohttp/ # aiohttp backend |
| 58 | +│ ├── websocket_client/ # websocket-client backend |
| 59 | +│ └── websockets/ # websockets backend |
| 60 | +├── oauth/ # OAuth V2 + OpenID Connect flows |
| 61 | +│ ├── installation_store/ # Token storage (file, SQLAlchemy, S3, etc.) |
| 62 | +│ ├── state_store/ # OAuth state management |
| 63 | +│ └── token_rotation/ # Token refresh logic |
| 64 | +├── models/ # Block Kit UI builders |
| 65 | +│ ├── blocks/ # Block elements |
| 66 | +│ ├── views/ # Modal views |
| 67 | +│ ├── attachments/ # Legacy attachments |
| 68 | +│ └── metadata/ # Event/entity metadata |
| 69 | +├── audit_logs/v1/ # Audit Logs API client |
| 70 | +├── scim/v1/ # SCIM API client |
| 71 | +├── signature/ # Request signature verification |
| 72 | +├── http_retry/ # HTTP retry handlers (builtin sync + async) |
| 73 | +├── rtm_v2/ # Real-time messaging (legacy) |
| 74 | +├── errors/ # Exception types |
| 75 | +└── version.py # Single source of truth for version |
| 76 | +
|
| 77 | +slack/ # Legacy package (maintenance mode, do not modify) |
| 78 | +``` |
| 79 | + |
| 80 | +### Code Generation (Critical Pattern) |
| 81 | + |
| 82 | +The SDK uses code generation to maintain sync/async/legacy variants from a single source of truth. This is the most important pattern to understand: |
| 83 | + |
| 84 | +1. **`slack_sdk/web/client.py`** is the canonical source for all Web API methods |
| 85 | +2. **`scripts/codegen.py`** transforms `client.py` into: |
| 86 | + - `async_client.py` — adds `async def`, `await`, replaces classes with async variants |
| 87 | + - `legacy_client.py` — adds `Union[Future, SlackResponse]` return types |
| 88 | +3. Similarly, `chat_stream.py` generates `async_chat_stream.py` |
| 89 | + |
| 90 | +**Never edit auto-generated files directly.** They contain a header: |
| 91 | + |
| 92 | +```text |
| 93 | +# DO NOT EDIT THIS FILE |
| 94 | +# 1) Modify slack_sdk/web/client.py |
| 95 | +# 2) Run `python scripts/codegen.py` |
| 96 | +# 3) Run `black slack_sdk/` |
| 97 | +``` |
| 98 | + |
| 99 | +After editing `client.py` or `chat_stream.py`, always run: |
| 100 | + |
| 101 | +```sh |
| 102 | +python scripts/codegen.py --path . |
| 103 | +./scripts/format.sh |
| 104 | +``` |
| 105 | + |
| 106 | +### Web API Method Pattern |
| 107 | + |
| 108 | +Every Web API method in `client.py` follows this pattern: |
| 109 | + |
| 110 | +```python |
| 111 | +def method_name( |
| 112 | + self, |
| 113 | + *, # keyword-only arguments |
| 114 | + required_param: str, |
| 115 | + optional_param: Optional[str] = None, |
| 116 | + **kwargs, |
| 117 | +) -> SlackResponse: |
| 118 | + """Description of the API method |
| 119 | + https://docs.slack.dev/reference/methods/method.name |
| 120 | + """ |
| 121 | + kwargs.update({"required_param": required_param}) |
| 122 | + if optional_param is not None: |
| 123 | + kwargs.update({"optional_param": optional_param}) |
| 124 | + return self.api_call("method.name", params=kwargs) |
| 125 | +``` |
| 126 | + |
| 127 | +Key conventions: |
| 128 | + |
| 129 | +- All parameters are keyword-only (after `*`) |
| 130 | +- Required params have no default; optional params default to `None` |
| 131 | +- `**kwargs` captures additional/undocumented parameters |
| 132 | +- Parameters are collected into `kwargs` dict and passed to `self.api_call()` |
| 133 | +- The `api_call` method name uses Slack's dot-notation (e.g., `"chat.postMessage"`) |
| 134 | +- Docstrings include a link to the Slack API reference |
| 135 | + |
| 136 | +### Error Types |
| 137 | + |
| 138 | +Defined in `slack_sdk/errors/__init__.py`: |
| 139 | + |
| 140 | +| Exception | When raised | |
| 141 | +| --- | --- | |
| 142 | +| `SlackClientError` | Base class for all client errors | |
| 143 | +| `SlackApiError` | Invalid API response (carries the `response` object) | |
| 144 | +| `SlackRequestError` | Problem submitting the request | |
| 145 | +| `BotUserAccessError` | `xoxb` token used for a `xoxp`-only method | |
| 146 | +| `SlackTokenRotationError` | `oauth.v2.access` token rotation failure | |
| 147 | +| `SlackClientNotConnectedError` | WebSocket operation while disconnected | |
| 148 | +| `SlackObjectFormationError` | Malformed Block Kit or other SDK objects | |
| 149 | +| `SlackClientConfigurationError` | Invalid client-side configuration | |
| 150 | + |
| 151 | +### HTTP Retry Handlers |
| 152 | + |
| 153 | +The `slack_sdk/http_retry/` module provides built-in retry strategies: |
| 154 | + |
| 155 | +- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`** — retries on connection errors |
| 156 | +- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** — retries on HTTP 429 (respects `Retry-After`) |
| 157 | +- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`** — retries on HTTP 500/503 |
| 158 | + |
| 159 | +Retry interval is configurable via `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. |
| 160 | + |
| 161 | +### Test Patterns |
| 162 | + |
| 163 | +Tests use `unittest.TestCase` with a mock web API server: |
| 164 | + |
| 165 | +```python |
| 166 | +import unittest |
| 167 | +from slack_sdk import WebClient |
| 168 | +from tests.slack_sdk.web.mock_web_api_handler import MockHandler |
| 169 | +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server |
| 170 | + |
| 171 | +class TestFeature(unittest.TestCase): |
| 172 | + def setUp(self): |
| 173 | + setup_mock_web_api_server(self, MockHandler) |
| 174 | + self.client = WebClient( |
| 175 | + token="xoxb-api_test", |
| 176 | + base_url="http://localhost:8888", |
| 177 | + ) |
| 178 | + |
| 179 | + def tearDown(self): |
| 180 | + cleanup_mock_web_api_server(self) |
| 181 | + |
| 182 | + def test_something(self): |
| 183 | + resp = self.client.api_test() |
| 184 | + self.assertTrue(resp["ok"]) |
| 185 | +``` |
| 186 | + |
| 187 | +Each sub-package has its own `MockHandler` (e.g., `tests/slack_sdk/webhook/mock_web_api_handler.py`, `tests/slack_sdk/scim/mock_web_api_handler.py`). Use the handler from the matching sub-package. |
| 188 | + |
| 189 | +Test directories: |
| 190 | + |
| 191 | +- `tests/` — Unit tests (mirroring `slack_sdk/` structure) |
| 192 | + - `tests/slack_sdk/` — Sync tests |
| 193 | + - `tests/slack_sdk_async/` — Async variants |
| 194 | + - `tests/slack_sdk_fixture/` — Pytest fixtures and test data |
| 195 | + - `tests/data/` — JSON fixture files |
| 196 | + - `tests/mock_web_api_server/` — Mock Slack API server |
| 197 | +- `integration_tests/` — Tests against real Slack APIs (require env tokens) |
| 198 | + |
| 199 | +## Development Commands |
| 200 | + |
| 201 | +### Setup |
| 202 | + |
| 203 | +A python virtual environment (`venv`) MUST be activated before running any commands. You can verify that the virtual environment is active by checking if the `$VIRTUAL_ENV` environment variable is set with `echo $VIRTUAL_ENV`. |
| 204 | +If tools like `black`, `flake8`, `mypy`, or `pytest` are not found, ask the user to activate the venv. |
| 205 | + |
| 206 | +Always use the project scripts instead of calling tools like `pytest` directly. |
| 207 | + |
| 208 | +### Install Dependencies |
| 209 | + |
| 210 | +```sh |
| 211 | +./scripts/install.sh |
| 212 | +``` |
| 213 | + |
| 214 | +Installs all project dependencies (testing, optional, and tools) via pip. This script is called automatically by `run_validation.sh`, `run_integration_tests.sh`, and `run_mypy.sh`, so you typically don't need to run it separately. |
| 215 | + |
| 216 | +### Full Validation |
| 217 | + |
| 218 | +```sh |
| 219 | +./scripts/run_validation.sh |
| 220 | +``` |
| 221 | + |
| 222 | +This is the canonical check — CI runs this and the PR template asks contributors to run it. It installs requirements, runs codegen, formats, lints, runs tests with coverage, and runs mypy type checking on Python 3.14. |
| 223 | + |
| 224 | +### Individual Commands |
| 225 | + |
| 226 | +| Task | Command | |
| 227 | +| --------------------------- | -------------------------------------------------------------------- | |
| 228 | +| Install dependencies | `./scripts/install.sh` | |
| 229 | +| Uninstall all packages | `./scripts/uninstall_all.sh` | |
| 230 | +| Format code | `./scripts/format.sh` | |
| 231 | +| Lint (check formatting) | `./scripts/lint.sh` | |
| 232 | +| Run all unit tests | `./scripts/run_tests.sh` | |
| 233 | +| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` | |
| 234 | +| Run type checking | `./scripts/run_mypy.sh` | |
| 235 | +| Generate async/legacy code | `python scripts/codegen.py --path .` | |
| 236 | +| Build PyPI package | `./scripts/build_pypi_package.sh` | |
| 237 | +| Generate API docs | `./scripts/generate_api_docs.sh` | |
| 238 | + |
| 239 | +## Code Style & Tooling |
| 240 | + |
| 241 | +- **Formatter**: `black` (line length: 125, version pinned in `requirements/tools.txt`) |
| 242 | +- **Linter**: `flake8` (line length: 125, configured in `.flake8`) |
| 243 | +- **Type checker**: `mypy` (configured in `pyproject.toml`, excludes `scim/` and `rtm/`) |
| 244 | +- **Test runner**: `pytest` with `pytest-asyncio` (asyncio_mode = "auto") |
| 245 | +- **Coverage**: `pytest-cov` reporting to Codecov |
| 246 | +- **Build system**: `setuptools` via `pyproject.toml` |
| 247 | + |
| 248 | +## CI Pipeline (GitHub Actions) |
| 249 | + |
| 250 | +Defined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, and daily schedule. Check the workflow file for the current Python version matrix. |
| 251 | + |
| 252 | +## Key Files |
| 253 | + |
| 254 | +| File | Purpose | |
| 255 | +| ----------------------------------- | -------------------------------------------------------- | |
| 256 | +| `slack_sdk/version.py` | Single source of truth for package version | |
| 257 | +| `slack_sdk/web/client.py` | Canonical Web API client (~200+ methods) | |
| 258 | +| `slack_sdk/web/chat_stream.py` | Canonical streaming chat client | |
| 259 | +| `scripts/codegen.py` | Code generator for async/legacy client variants | |
| 260 | +| `pyproject.toml` | Project config (build, black, pytest, mypy settings) | |
| 261 | +| `.flake8` | Flake8 linting config | |
| 262 | +| `requirements/testing.txt` | Test dependencies | |
| 263 | +| `requirements/optional.txt` | Optional runtime dependencies (aiohttp, SQLAlchemy, etc) | |
| 264 | +| `requirements/tools.txt` | Dev tools (black, flake8, mypy) | |
| 265 | +| `.github/workflows/ci-build.yml` | CI pipeline definition | |
| 266 | +| `.github/maintainers_guide.md` | Maintainer workflows and release process | |
| 267 | + |
| 268 | +## Common Contribution Workflows |
| 269 | + |
| 270 | +### Adding a New Web API Method |
| 271 | + |
| 272 | +1. Add the method to `slack_sdk/web/client.py` following the existing pattern |
| 273 | +2. Run code generation: `python scripts/codegen.py --path .` |
| 274 | +3. Run formatter: `./scripts/format.sh` |
| 275 | +4. Add tests in `tests/slack_sdk/web/` |
| 276 | +5. Validate: `./scripts/run_validation.sh` |
| 277 | + |
| 278 | +### Adding a New Feature to a Non-Web Module |
| 279 | + |
| 280 | +1. Implement the sync version in the appropriate `slack_sdk/` subpackage |
| 281 | +2. If the module has async variants, implement those as well (not auto-generated for non-web modules) |
| 282 | +3. Add tests mirroring the module structure |
| 283 | +4. Validate: `./scripts/run_validation.sh` |
| 284 | + |
| 285 | +### Fixing a Bug |
| 286 | + |
| 287 | +1. Write a test that reproduces the bug |
| 288 | +2. Fix the code (if in `client.py`, run codegen afterward) |
| 289 | +3. Validate: `./scripts/run_validation.sh` |
| 290 | + |
| 291 | +## Versioning & Releases |
| 292 | + |
| 293 | +- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it. |
| 294 | +- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/) |
| 295 | +- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml` |
| 296 | +- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this |
| 297 | +- Commit message format for releases: `chore(release): version X.Y.Z` |
| 298 | + |
| 299 | +## Dependencies |
| 300 | + |
| 301 | +The SDK has **zero required runtime dependencies** for the core sync Web API client and it is imperative it remains that way. Optional dependencies enable additional functionality: |
| 302 | + |
| 303 | +- `aiohttp` — async HTTP client |
| 304 | +- `websockets` / `websocket-client` — Socket Mode backends |
| 305 | +- `SQLAlchemy` — OAuth token storage |
| 306 | +- `boto3` — S3/DynamoDB token storage |
| 307 | +- `aiodns` — faster DNS resolution for async |
| 308 | + |
| 309 | +Version constraints for optional and dev dependencies are pinned in the `requirements/` directory. |
0 commit comments