Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CLAUDE.local.md
settings.local.json
worktrees/
plans/
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../AGENTS.md
36 changes: 36 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"permissions": {
"allow": [
"Bash(./scripts/build_pypi_package.sh:*)",
"Bash(./scripts/format.sh:*)",
"Bash(./scripts/generate_api_docs.sh:*)",
"Bash(./scripts/install.sh:*)",
"Bash(./scripts/lint.sh:*)",
"Bash(./scripts/run_mypy.sh:*)",
"Bash(./scripts/run_tests.sh:*)",
"Bash(./scripts/run_validation.sh:*)",
"Bash(./scripts/uninstall_all.sh:*)",
"Bash(python scripts/codegen.py:*)",
"Bash(echo $VIRTUAL_ENV)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐍 praise: Super nice command to have here!

"Bash(gh issue view:*)",
"Bash(gh label list:*)",
"Bash(gh pr checks:*)",
"Bash(gh pr diff:*)",
"Bash(gh pr list:*)",
"Bash(gh pr status:*)",
"Bash(gh pr update-branch:*)",
"Bash(gh pr view:*)",
"Bash(gh search code:*)",
"Bash(git diff:*)",
"Bash(git grep:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git status:*)",
"Bash(grep:*)",
"Bash(ls:*)",
"Bash(tree:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:docs.slack.dev)"
]
}
}
4 changes: 2 additions & 2 deletions .github/maintainers_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ Run all the unit tests, code linter, and code analyzer:
Run all the unit tests (no linter nor code analyzer):

```sh
./scripts/run_unit_tests.sh
./scripts/run_tests.sh
```

Run a specific unit test:

```sh
./scripts/run_unit_tests.sh tests/web/test_web_client.py
./scripts/run_tests.sh tests/web/test_web_client.py
```

You can rely on GitHub Actions builds for running the tests on a variety of Python runtimes.
Expand Down
308 changes: 308 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
# AGENTS.md — Python Slack SDK

## Project Overview

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.

- **Repository**: <https://github.com/slackapi/python-slack-sdk>
- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/>
- **PyPI**: <https://pypi.org/project/slack-sdk/>
- **Current version**: defined in `slack_sdk/version.py`

## Critical Rules

These are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code:

1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly:
- `slack_sdk/web/async_client.py`
- `slack_sdk/web/legacy_client.py`
- `slack_sdk/web/async_chat_stream.py`

Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)).

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`.

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/`.

4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:**

```sh
python scripts/codegen.py --path .
./scripts/format.sh
```

5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting.

## Architecture

### Package Structure

The SDK is organized into independent sub-packages:

- **`slack_sdk/web/`** — Web API client (sync, async, legacy). Contains auto-generated files (see [Code Generation](#code-generation-critical-pattern))
- **`slack_sdk/webhook/`** — Incoming Webhooks
- **`slack_sdk/socket_mode/`** — Socket Mode with pluggable backends
- **`slack_sdk/oauth/`** — OAuth flows and token storage
- **`slack_sdk/models/`** — Block Kit UI builders
- **`slack_sdk/audit_logs/`**, **`slack_sdk/scim/`** — Enterprise APIs
- **`slack_sdk/signature/`** — Request verification
- **`slack_sdk/http_retry/`** — Retry handlers
- **`slack/`** — Legacy package (maintenance mode, do not modify)

See the repository structure for the complete package layout.

### Code Generation (Critical Pattern)

**NEVER edit these auto-generated files:**

- `slack_sdk/web/async_client.py`
- `slack_sdk/web/legacy_client.py`
- `slack_sdk/web/async_chat_stream.py`

Each contains a header warning:
```text
# DO NOT EDIT THIS FILE
# 1) Modify slack_sdk/web/client.py
# 2) Run `python scripts/codegen.py`
# 3) Run `black slack_sdk/`
```

**How it works:**

1. Edit `slack_sdk/web/client.py` (canonical source for Web API methods)
2. Edit `slack_sdk/web/chat_stream.py` (canonical source for streaming chat)
3. Run `python scripts/codegen.py --path .` to generate async/legacy variants
4. Run `./scripts/format.sh` to format the generated code

The codegen script (`scripts/codegen.py`) automatically transforms sync code into async variants by adding `async def`, `await`, and replacing classes with async equivalents.

### Web API Method Pattern

Every Web API method in `client.py` follows this pattern:

```python
def method_name(
self,
*, # keyword-only arguments
required_param: str,
optional_param: Optional[str] = None,
**kwargs,
) -> SlackResponse:
"""Description of the API method
https://docs.slack.dev/reference/methods/method.name
"""
kwargs.update({"required_param": required_param})
if optional_param is not None:
kwargs.update({"optional_param": optional_param})
return self.api_call("method.name", params=kwargs)
```

Key conventions:

- All parameters are keyword-only (after `*`)
- Required params have no default; optional params default to `None`
- `**kwargs` captures additional/undocumented parameters
- Parameters are collected into `kwargs` dict and passed to `self.api_call()`
- The `api_call` method name uses Slack's dot-notation (e.g., `"chat.postMessage"`)
- Docstrings include a link to the Slack API reference

### Error Types

All SDK exceptions are defined in `slack_sdk/errors/__init__.py` and inherit from `SlackClientError`.

Key exceptions to be aware of:

- **`SlackApiError`** — Raised when the API returns an error response (carries the `response` object)
- **`SlackRequestError`** — Raised when the HTTP request itself fails
- **`BotUserAccessError`** — Raised when using a bot token (`xoxb-*`) for a user-only method

See `slack_sdk/errors/__init__.py` for the complete list of exception types and their usage.

### HTTP Retry Handlers

The `slack_sdk/http_retry/` module provides built-in retry strategies for connection errors, rate limiting (HTTP 429), and server errors (HTTP 500/503).

Key handlers:

- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`**
- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** (respects `Retry-After` header)
- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`**

Retry intervals can be configured with `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. See `slack_sdk/http_retry/` for implementation details.

### Test Patterns

Tests use `unittest.TestCase` with a mock web API server:

```python
import unittest
from slack_sdk import WebClient
from tests.slack_sdk.web.mock_web_api_handler import MockHandler
from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server

class TestFeature(unittest.TestCase):
def setUp(self):
setup_mock_web_api_server(self, MockHandler)
self.client = WebClient(
token="xoxb-api_test",
base_url="http://localhost:8888",
)

def tearDown(self):
cleanup_mock_web_api_server(self)

def test_something(self):
resp = self.client.api_test()
self.assertTrue(resp["ok"])
```

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.

Test directories:

- `tests/` — Unit tests (mirroring `slack_sdk/` structure)
- `tests/slack_sdk/` — Sync tests
- `tests/slack_sdk_async/` — Async variants
- `tests/slack_sdk_fixture/` — Pytest fixtures and test data
- `tests/data/` — JSON fixture files
- `tests/mock_web_api_server/` — Mock Slack API server
- `integration_tests/` — Tests against real Slack APIs (require env tokens)

## Development Commands

### Setup

**Prerequisites:** A Python virtual environment must be activated. See `.github/maintainers_guide.md` for detailed setup instructions using `pyenv` and `venv`.

**Quick check:** Verify venv is active with `echo $VIRTUAL_ENV` (should output a path).

Always use the project scripts instead of calling tools like `pytest` directly.

### Install Dependencies

```sh
./scripts/install.sh
```

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.

### Full Validation

```sh
./scripts/run_validation.sh
```

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 the latest supported Python version (check the `LATEST_SUPPORTED_PY` environment variable in `.github/workflows/ci-build.yml`).

### Individual Commands

Available scripts in the `scripts/` directory:

| Task | Command |
| --- | --- |
| Install dependencies | `./scripts/install.sh` |
| Uninstall all packages | `./scripts/uninstall_all.sh` |
| Format code | `./scripts/format.sh` |
| Lint (check formatting) | `./scripts/lint.sh` |
| Run all unit tests | `./scripts/run_tests.sh` |
| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` |
| Run type checking | `./scripts/run_mypy.sh` |
| Generate async/legacy code | `python scripts/codegen.py --path .` |
| Build PyPI package | `./scripts/build_pypi_package.sh` |
| Generate API docs | `./scripts/generate_api_docs.sh` |

## Code Style & Tooling

All tooling configuration is defined in the following files:

- **Formatter**: `black` — see `[tool.black]` in `pyproject.toml`
- **Linter**: `flake8` — see `.flake8`
- **Type checker**: `mypy` — see `[tool.mypy]` in `pyproject.toml`
- **Test runner**: `pytest` — see `[tool.pytest.ini_options]` in `pyproject.toml`
- **Coverage**: `pytest-cov` reporting to Codecov
- **Build system**: see `[build-system]` and `[project]` in `pyproject.toml`

**Dependencies:**

- Testing: `requirements/testing.txt`
- Optional runtime: `requirements/optional.txt`
- Dev tools (black, flake8, mypy): `requirements/tools.txt`

## CI Pipeline (GitHub Actions)

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.

## Key Files & Directories

**Source Code:**

- `slack_sdk/` — Main package (active development)
- `slack_sdk/web/client.py` — **CANONICAL SOURCE** for all Web API methods
- `slack_sdk/web/chat_stream.py` — Canonical streaming chat client
- `slack_sdk/version.py` — Single source of truth for version
- `slack/` — Legacy package (maintenance mode, DO NOT MODIFY)

**Configuration:**

- `pyproject.toml` — Project metadata, build config, tool settings (black, pytest, mypy)
- `.flake8` — Flake8 linter configuration
- `requirements/*.txt` — Dependency specifications

**Tooling:**

- `scripts/codegen.py` — Generates async/legacy client variants
- `scripts/*.sh` — Development and CI helper scripts

**GitHub & CI/CD:**

- `.github/` — GitHub-specific configuration and documentation
- `.github/workflows/` — Continuous integration pipeline definitions that run on GitHub Actions
- `.github/maintainers_guide.md` — Maintainer workflows and release process

**Documentation:**

- `README.md` — Project overview, installation, and usage examples
- `.github/maintainers_guide.md` — Maintainer workflows and release process

## Common Contribution Workflows

### Adding a New Web API Method

1. Add the method to `slack_sdk/web/client.py` following the existing pattern
2. Run code generation: `python scripts/codegen.py --path .`
3. Run formatter: `./scripts/format.sh`
4. Add tests in `tests/slack_sdk/web/`
5. Validate: `./scripts/run_validation.sh`

### Adding a New Feature to a Non-Web Module

1. Implement the sync version in the appropriate `slack_sdk/` subpackage
2. If the module has async variants, implement those as well (not auto-generated for non-web modules)
3. Add tests mirroring the module structure
4. Validate: `./scripts/run_validation.sh`

### Fixing a Bug

1. Write a test that reproduces the bug
2. Fix the code (if in `client.py`, run codegen afterward)
3. Validate: `./scripts/run_validation.sh`

## Versioning & Releases

- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it.
- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/)
- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml`
- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this
- Commit message format for releases: `chore(release): version X.Y.Z`

## Dependencies

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:

- `aiohttp` — async HTTP client
- `websockets` / `websocket-client` — Socket Mode backends
- `SQLAlchemy` — OAuth token storage
- `boto3` — S3/DynamoDB token storage
- `aiodns` — faster DNS resolution for async

Version constraints for optional and dev dependencies are pinned in the `requirements/` directory.
14 changes: 14 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
# ./scripts/install.sh
# Installs all project dependencies (testing, optional, and tools)

set -e

script_dir=$(dirname $0)
cd ${script_dir}/..

pip install -U pip

pip install -U -r requirements/testing.txt \
-U -r requirements/optional.txt \
-U -r requirements/tools.txt
5 changes: 1 addition & 4 deletions scripts/run_integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ set -e
script_dir=`dirname $0`
cd ${script_dir}/..

pip install -U pip
pip install -U -r requirements/testing.txt \
-U -r requirements/optional.txt \
-U -r requirements/tools.txt
./scripts/install.sh

echo "Generating code ..." && python scripts/codegen.py --path .
echo "Running black (code formatter) ..." && ./scripts/format.sh --no-install
Expand Down
Loading