Skip to content

Commit 5933cb0

Browse files
chore: add AGENTS.md and consolidate development scripts
1 parent f14e689 commit 5933cb0

File tree

10 files changed

+390
-20
lines changed

10 files changed

+390
-20
lines changed

.claude/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CLAUDE.local.md
2+
settings.local.json
3+
worktrees/
4+
plans/

.claude/CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# CLAUDE.md
2+
3+
Persistent context, coding standards, and instructions for AI coding agents have been centralized to `AGENTS.md`.
4+
5+
**CRITICAL INSTRUCTION:** Before answering any questions, writing any code, or executing any commands in this repository, you MUST read the `AGENTS.md` file located in the root directory.
6+
7+
Proceed by silently reading `./AGENTS.md`, do not ask for permission to read it; just do it.

.claude/settings.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(./scripts/build_pypi_package.sh:*)",
5+
"Bash(./scripts/format.sh:*)",
6+
"Bash(./scripts/generate_api_docs.sh:*)",
7+
"Bash(./scripts/install.sh:*)",
8+
"Bash(./scripts/install_all_and_run_tests.sh:*)",
9+
"Bash(./scripts/lint.sh:*)",
10+
"Bash(./scripts/run_mypy.sh:*)",
11+
"Bash(./scripts/run_tests.sh:*)",
12+
"Bash(./scripts/run_validation.sh:*)",
13+
"Bash(./scripts/uninstall_all.sh:*)",
14+
"Bash(python scripts/codegen.py:*)",
15+
"Bash(echo $VIRTUAL_ENV)",
16+
"Bash(gh issue view:*)",
17+
"Bash(gh label list:*)",
18+
"Bash(gh pr checks:*)",
19+
"Bash(gh pr diff:*)",
20+
"Bash(gh pr list:*)",
21+
"Bash(gh pr status:*)",
22+
"Bash(gh pr update-branch:*)",
23+
"Bash(gh pr view:*)",
24+
"Bash(gh search code:*)",
25+
"Bash(git diff:*)",
26+
"Bash(git grep:*)",
27+
"Bash(git log:*)",
28+
"Bash(git show:*)",
29+
"Bash(git status:*)",
30+
"Bash(grep:*)",
31+
"Bash(ls:*)",
32+
"Bash(tree:*)",
33+
"WebFetch(domain:github.com)",
34+
"WebFetch(domain:docs.slack.dev)"
35+
]
36+
}
37+
}

.github/maintainers_guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ Run all the unit tests, code linter, and code analyzer:
8888
Run all the unit tests (no linter nor code analyzer):
8989

9090
```sh
91-
./scripts/run_unit_tests.sh
91+
./scripts/run_tests.sh
9292
```
9393

9494
Run a specific unit test:
9595

9696
```sh
97-
./scripts/run_unit_tests.sh tests/web/test_web_client.py
97+
./scripts/run_tests.sh tests/web/test_web_client.py
9898
```
9999

100100
You can rely on GitHub Actions builds for running the tests on a variety of Python runtimes.

AGENTS.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
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.

scripts/install.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
# ./scripts/install.sh
3+
# Installs all project dependencies (testing, optional, and tools)
4+
5+
set -e
6+
7+
script_dir=$(dirname $0)
8+
cd ${script_dir}/..
9+
10+
pip install -U pip
11+
12+
pip install -U -r requirements/testing.txt \
13+
-U -r requirements/optional.txt \
14+
-U -r requirements/tools.txt

0 commit comments

Comments
 (0)