Skip to content

Commit 501ed84

Browse files
Improve based on feedback
1 parent d644857 commit 501ed84

File tree

8 files changed

+242
-130
lines changed

8 files changed

+242
-130
lines changed

.claude/.gitignore

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

.claude/CLAUDE.md

Lines changed: 1 addition & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1 @@
1-
# CLAUDE.md
2-
3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4-
5-
## Project Overview
6-
7-
Slack Bolt for Python -- a framework for building Slack apps. Built on top of `slack_sdk>=3.38.0,<4`. Supports both sync (`App`) and async (`AsyncApp`) patterns, with adapters for Flask, FastAPI, Django, AWS Lambda, and many other frameworks.
8-
9-
## Environment Setup
10-
11-
A virtual environment (`.venv`) should be activated before running any commands. If tools like `black`, `flake8`, or `pytest` are not found, ask the user to activate the venv.
12-
13-
## Common Commands
14-
15-
### Testing
16-
17-
Always use the project scripts instead of calling `pytest` directly:
18-
```bash
19-
# Run all tests (installs deps, formats, lints, tests, typechecks)
20-
./scripts/install_all_and_run_tests.sh
21-
22-
# Run a single test file
23-
./scripts/run_tests.sh tests/scenario_tests/test_app.py
24-
25-
# Run a single test function
26-
./scripts/run_tests.sh tests/scenario_tests/test_app.py::TestApp::test_name
27-
```
28-
29-
### Formatting, Linting, Type Checking
30-
```bash
31-
# Format (black, line-length=125)
32-
./scripts/format.sh --no-install
33-
34-
# Lint (flake8, line-length=125, ignores: F841,F821,W503,E402)
35-
./scripts/lint.sh --no-install
36-
37-
# Type check (mypy)
38-
./scripts/run_mypy.sh --no-install
39-
```
40-
41-
### First-Time Setup
42-
```bash
43-
pip install -U -e .
44-
pip install -U -r requirements/testing.txt
45-
pip install -U -r requirements/adapter.txt
46-
pip install -U -r requirements/adapter_testing.txt
47-
pip install -U -r requirements/tools.txt
48-
```
49-
50-
## Architecture
51-
52-
### Request Processing Pipeline
53-
54-
Incoming requests flow through a middleware chain before reaching listeners:
55-
56-
1. **SSL Check** -> **Request Verification** (signature) -> **URL Verification** -> **Authorization** (token injection) -> **Ignoring Self Events** -> Custom middleware
57-
2. **Listener Matching** -- `ListenerMatcher` implementations check if a listener should handle the request
58-
3. **Listener Execution** -- listener-specific middleware runs, then `ack()` is called, then the handler executes
59-
60-
For FaaS environments (`process_before_response=True`), long-running handlers execute as "lazy listeners" in a thread pool after the ack response is returned.
61-
62-
### Core Abstractions
63-
64-
- **`App` / `AsyncApp`** (`slack_bolt/app/`) -- Central class. Registers listeners via decorators (`@app.event()`, `@app.action()`, `@app.command()`, `@app.message()`, `@app.view()`, `@app.shortcut()`, `@app.options()`, `@app.function()`). Dispatches incoming requests through middleware to matching listeners.
65-
- **`Middleware`** (`slack_bolt/middleware/`) -- Abstract base with `process(req, resp, next)`. Built-in: authorization, request verification, SSL check, URL verification, assistant, self-event ignoring.
66-
- **`Listener`** (`slack_bolt/listener/`) -- Has matchers, middleware, and an ack/handler function. `CustomListener` is the main implementation.
67-
- **`ListenerMatcher`** (`slack_bolt/listener_matcher/`) -- Determines if a listener handles a given request. Built-in matchers for events, actions, commands, messages (regex), shortcuts, views, options.
68-
- **`BoltContext`** (`slack_bolt/context/`) -- Dict-like object passed to listeners with `client`, `say()`, `ack()`, `respond()`, `complete()`, `fail()`, plus event metadata (`user_id`, `channel_id`, `team_id`).
69-
- **`BoltRequest` / `BoltResponse`** (`slack_bolt/request/`, `slack_bolt/response/`) -- Request/response wrappers. Request has `mode` of "http" or "socket_mode".
70-
71-
### Kwargs Injection
72-
73-
Listeners receive arguments by parameter name. The framework inspects function signatures and injects matching args: `body`, `event`, `action`, `command`, `payload`, `context`, `client`, `ack`, `say`, `respond`, `logger`, `complete`, `fail`, `agent`, etc. Defined in `slack_bolt/kwargs_injection/args.py`.
74-
75-
### Adapter System
76-
77-
Each adapter in `slack_bolt/adapter/` converts between a web framework's request/response types and `BoltRequest`/`BoltResponse`. Adapters exist for: Flask, FastAPI, Django, Starlette, Sanic, Bottle, Tornado, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, Socket Mode, WSGI, ASGI, and more.
78-
79-
### Async Support
80-
81-
`AsyncApp` mirrors `App` with async middleware, listeners, and context utilities. Located in `slack_bolt/app/async_app.py`. Requires `aiohttp`. All async variants live alongside their sync counterparts (e.g., `async_middleware.py` next to `middleware.py`).
82-
83-
### AI Agents & Assistants
84-
85-
`BoltAgent` (`slack_bolt/agent/`) provides `chat_stream()`, `set_status()`, and `set_suggested_prompts()` for AI-powered agents. `Assistant` middleware (`slack_bolt/middleware/assistant/`) handles assistant thread events.
86-
87-
## Test Organization
88-
89-
- `tests/scenario_tests/` -- Integration-style tests with realistic Slack payloads
90-
- `tests/slack_bolt/` -- Unit tests mirroring the source structure
91-
- `tests/adapter_tests/` and `tests/adapter_tests_async/` -- Framework adapter tests
92-
- `tests/mock_web_api_server/` -- Mock Slack API server used by tests
93-
- Async test variants use `_async` suffix directories
94-
95-
## Code Style
96-
97-
- **Black** formatter, 125 char line length
98-
- **Flake8** linter, 125 char line length
99-
- **MyPy** with `force_union_syntax=true` and `warn_unused_ignores=true` (use `X | Y` union syntax, not `Union[X, Y]`)
100-
- pytest with `asyncio_mode = "auto"`
1+
@../AGENTS.md

.claude/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"Bash(./scripts/lint.sh:*)",
88
"Bash(./scripts/run_mypy.sh:*)",
99
"Bash(./scripts/run_tests.sh:*)",
10+
"Bash(./scripts/install.sh:*)",
11+
"Bash(echo $VIRTUAL_ENV)",
1012
"Bash(gh issue view:*)",
1113
"Bash(gh label list:*)",
1214
"Bash(gh pr checks:*)",
@@ -25,7 +27,8 @@
2527
"Bash(ls:*)",
2628
"Bash(tree:*)",
2729
"WebFetch(domain:github.com)",
28-
"WebFetch(domain:docs.slack.dev)"
30+
"WebFetch(domain:docs.slack.dev)",
31+
"WebFetch(domain:raw.githubusercontent.com)"
2932
]
3033
}
3134
}

AGENTS.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# AGENTS.md - bolt-python
2+
3+
## Project Overview
4+
5+
Slack Bolt for Python -- a framework for building Slack apps in Python.
6+
7+
- **Foundation:** Built on top of `slack_sdk` (see `pyproject.toml` constraints).
8+
- **Execution Models:** Supports both synchronous (`App`) and asynchronous (`AsyncApp` using `asyncio`) execution. Async mode requires `aiohttp` as an additional dependency.
9+
- **Framework Adapters:** Features built-in adapters for web frameworks (Flask, FastAPI, Django, Tornado, Pyramid, and many more) and serverless environments (AWS Lambda, Google Cloud Functions).
10+
- **Python Version:** Requires Python 3.7+ as defined in `pyproject.toml`.
11+
12+
- **Repository**: <https://github.com/slackapi/bolt-python>
13+
- **Documentation**: <https://docs.slack.dev/tools/bolt-python/>
14+
- **PyPI**: <https://pypi.org/project/slack-bolt/>
15+
- **Current version**: defined in `slack_bolt/version.py` (referenced by `pyproject.toml` via `[tool.setuptools.dynamic]`)
16+
17+
## Environment Setup
18+
19+
A python virtual environment (`venv`) should be activated before running any commands.
20+
21+
```bash
22+
# Create a venv (first time only)
23+
python -m venv .venv
24+
25+
# Activate
26+
source .venv/bin/activate
27+
28+
# Install all dependencies
29+
./scripts/install.sh
30+
```
31+
32+
You can verify the venv is active by checking `echo $VIRTUAL_ENV`. If tools like `black`, `flake8`, `mypy` or `pytest` are not found, ask the user to activate the venv.
33+
34+
## Common Commands
35+
36+
### Testing
37+
38+
Always use the project scripts instead of calling `pytest` directly:
39+
40+
```bash
41+
# Install all dependencies and run all tests (formats, lints, tests, typechecks)
42+
./scripts/install_all_and_run_tests.sh
43+
44+
# Run a single test file
45+
./scripts/run_tests.sh tests/scenario_tests/test_app.py
46+
47+
# Run a single test function
48+
./scripts/run_tests.sh tests/scenario_tests/test_app.py::TestApp::test_name
49+
```
50+
51+
### Formatting, Linting, Type Checking
52+
53+
```bash
54+
# Format (black, line-length=125)
55+
./scripts/format.sh --no-install
56+
57+
# Lint (flake8, line-length=125, ignores: F841,F821,W503,E402)
58+
./scripts/lint.sh --no-install
59+
60+
# Type check (mypy)
61+
./scripts/run_mypy.sh --no-install
62+
```
63+
64+
## Architecture
65+
66+
### Request Processing Pipeline
67+
68+
Incoming requests flow through a middleware chain before reaching listeners:
69+
70+
1. **SSL Check** -> **Request Verification** (signature) -> **URL Verification** -> **Authorization** (token injection) -> **Ignoring Self Events** -> Custom middleware
71+
2. **Listener Matching** -- `ListenerMatcher` implementations check if a listener should handle the request
72+
3. **Listener Execution** -- listener-specific middleware runs, then `ack()` is called, then the handler executes
73+
74+
For FaaS environments (`process_before_response=True`), long-running handlers execute as "lazy listeners" in a thread pool after the ack response is returned.
75+
76+
### Core Abstractions
77+
78+
- **`App` / `AsyncApp`** (`slack_bolt/app/`) -- Central class. Registers listeners via decorators (`@app.event()`, `@app.action()`, `@app.command()`, `@app.message()`, `@app.view()`, `@app.shortcut()`, `@app.options()`, `@app.function()`). Dispatches incoming requests through middleware to matching listeners.
79+
- **`Middleware`** (`slack_bolt/middleware/`) -- Abstract base with `process(req, resp, next)`. Built-in: authorization, request verification, SSL check, URL verification, assistant, self-event ignoring.
80+
- **`Listener`** (`slack_bolt/listener/`) -- Has matchers, middleware, and an ack/handler function. `CustomListener` is the main implementation.
81+
- **`ListenerMatcher`** (`slack_bolt/listener_matcher/`) -- Determines if a listener handles a given request. Built-in matchers for events, actions, commands, messages (regex), shortcuts, views, options, functions.
82+
- **`BoltContext`** (`slack_bolt/context/`) -- Dict-like object passed to listeners with `client`, `say()`, `ack()`, `respond()`, `complete()`, `fail()`, plus event metadata (`user_id`, `channel_id`, `team_id`, etc.).
83+
- **`BoltRequest` / `BoltResponse`** (`slack_bolt/request/`, `slack_bolt/response/`) -- Request/response wrappers. Request has `mode` of "http" or "socket_mode".
84+
85+
### Kwargs Injection
86+
87+
Listeners receive arguments by parameter name. The framework inspects function signatures and injects matching args: `body`, `event`, `action`, `command`, `payload`, `context`, `client`, `ack`, `say`, `respond`, `logger`, `complete`, `fail`, `agent`, etc. Defined in `slack_bolt/kwargs_injection/args.py`.
88+
89+
### Adapter System
90+
91+
Each adapter in `slack_bolt/adapter/` converts between a web framework's request/response types and `BoltRequest`/`BoltResponse`. Adapters exist for: Flask, FastAPI, Django, Starlette, Sanic, Bottle, Tornado, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, Socket Mode, WSGI, ASGI, and more.
92+
93+
### Sync/Async Mirroring Pattern
94+
95+
**This is the most important pattern in this codebase.** Almost every module has both a sync and async variant. When you modify one, you almost always must modify the other.
96+
97+
**File naming convention:** Async files use the `async_` prefix alongside their sync counterpart:
98+
99+
```text
100+
slack_bolt/middleware/custom_middleware.py # sync
101+
slack_bolt/middleware/async_custom_middleware.py # async
102+
103+
slack_bolt/context/say/say.py # sync
104+
slack_bolt/context/say/async_say.py # async
105+
106+
slack_bolt/listener/custom_listener.py # sync
107+
slack_bolt/listener/async_listener.py # async
108+
109+
slack_bolt/adapter/fastapi/async_handler.py # async-only (no sync FastAPI adapter)
110+
slack_bolt/adapter/flask/handler.py # sync-only (no async Flask adapter)
111+
```
112+
113+
**Which modules come in sync/async pairs:**
114+
115+
- `slack_bolt/app/` -- `app.py` / `async_app.py`
116+
- `slack_bolt/middleware/` -- every middleware has an `async_` counterpart
117+
- `slack_bolt/listener/` -- `listener.py` / `async_listener.py`, plus error/completion/start handlers
118+
- `slack_bolt/listener_matcher/` -- `builtins.py` / `async_builtins.py`
119+
- `slack_bolt/context/` -- each subdirectory (e.g., `say/`, `ack/`, `respond/`) has `async_` variants
120+
- `slack_bolt/kwargs_injection/` -- `args.py` / `async_args.py`, `utils.py` / `async_utils.py`
121+
122+
**Adapters are an exception:** Most adapters are sync-only or async-only depending on the framework. Async-native frameworks (FastAPI, Starlette, Sanic, Tornado, ASGI, Socket Mode) have `async_handler.py`. Sync-only frameworks (Flask, Django, Bottle, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, WSGI) have `handler.py`.
123+
124+
### AI Agents & Assistants
125+
126+
`BoltAgent` (`slack_bolt/agent/`) provides `chat_stream()`, `set_status()`, and `set_suggested_prompts()` for AI-powered agents. `Assistant` middleware (`slack_bolt/middleware/assistant/`) handles assistant thread events.
127+
128+
## Key Development Patterns
129+
130+
### Adding or Modifying Middleware
131+
132+
1. Implement the sync version in `slack_bolt/middleware/` (subclass `Middleware`, implement `process()`)
133+
2. Implement the async version with `async_` prefix (subclass `AsyncMiddleware`, implement `async_process()`)
134+
3. Export built-in middleware from `slack_bolt/middleware/__init__.py` (sync) and `async_builtins.py` (async)
135+
136+
### Adding a Context Utility
137+
138+
Each context utility lives in its own subdirectory under `slack_bolt/context/`:
139+
140+
```text
141+
slack_bolt/context/my_util/
142+
__init__.py
143+
my_util.py # sync implementation
144+
async_my_util.py # async implementation
145+
internals.py # shared logic (optional)
146+
```
147+
148+
Then wire it into `BoltContext` (`slack_bolt/context/context.py`) and `AsyncBoltContext` (`slack_bolt/context/async_context.py`).
149+
150+
### Adding a New Adapter
151+
152+
1. Create `slack_bolt/adapter/<framework>/`
153+
2. Add `__init__.py` and `handler.py` (or `async_handler.py` for async frameworks)
154+
3. The handler converts the framework's request to `BoltRequest`, calls `app.dispatch()`, and converts `BoltResponse` back
155+
4. Add the framework to `requirements/adapter.txt` with version constraints
156+
5. Add adapter tests in `tests/adapter_tests/` (or `tests/adapter_tests_async/`)
157+
158+
### Adding a Kwargs-Injectable Argument
159+
160+
1. Add the new arg to `slack_bolt/kwargs_injection/args.py` and `async_args.py`
161+
2. Update the `Args` class with the new property
162+
3. Populate the arg in the appropriate context or listener setup code
163+
164+
## Dependencies
165+
166+
The core package has a **single required runtime dependency**: `slack_sdk` (defined in `pyproject.toml`). Do not add runtime dependencies.
167+
168+
**`requirements/` directory structure:**
169+
170+
- `async.txt` -- async runtime deps (`aiohttp`, `websockets`)
171+
- `adapter.txt` -- all framework adapter deps (Flask, Django, FastAPI, etc.)
172+
- `testing.txt` -- test runner deps (`pytest`, `pytest-asyncio`, includes `async.txt`)
173+
- `testing_without_asyncio.txt` -- test deps without async (`pytest`, `pytest-cov`)
174+
- `adapter_testing.txt` -- adapter-specific test deps (`moto`, `boddle`, `sanic-testing`)
175+
- `tools.txt` -- dev tools (`mypy`, `flake8`, `black`)
176+
177+
When adding a new dependency: add it to the appropriate `requirements/*.txt` file with version constraints, never to `pyproject.toml` `dependencies` (unless it's a core runtime dep, which is very rare).
178+
179+
## Test Organization
180+
181+
- `tests/scenario_tests/` -- Integration-style tests with realistic Slack payloads
182+
- `tests/slack_bolt/` -- Unit tests mirroring the source structure
183+
- `tests/adapter_tests/` and `tests/adapter_tests_async/` -- Framework adapter tests
184+
- `tests/mock_web_api_server/` -- Mock Slack API server used by tests
185+
- Async test variants use `_async` suffix directories
186+
187+
**Where to put new tests:** Mirror the source structure. For `slack_bolt/middleware/foo.py`, add tests in `tests/slack_bolt/middleware/test_foo.py`. For async variants, use the `_async` suffix directory or file naming pattern. Adapter tests go in `tests/adapter_tests/` (sync) or `tests/adapter_tests_async/` (async).
188+
189+
**Mock server:** Many tests use `tests/mock_web_api_server/` to simulate Slack API responses. Look at existing tests for usage patterns rather than making real API calls.
190+
191+
## Code Style
192+
193+
- **Black** formatter configured in `pyproject.toml` (line-length=125)
194+
- **Flake8** linter configured in `.flake8` (line-length=125, ignores: F841,F821,W503,E402)
195+
- **MyPy** configured in `pyproject.toml`
196+
- **pytest** configured in `pyproject.toml`
197+
198+
## GitHub & CI/CD
199+
200+
- `.github/` -- GitHub-specific configuration and documentation
201+
- `.github/workflows/` -- Continuous integration pipeline definitions that run on GitHub Actions
202+
- `.github/maintainers_guide.md` -- Maintainer workflows and release process

scripts/install.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
# Installs all dependencies of the project
3+
# ./scripts/install.sh
4+
5+
script_dir=`dirname $0`
6+
cd ${script_dir}/..
7+
rm -rf ./slack_bolt.egg-info
8+
9+
# Update pip to prevent warnings
10+
pip install -U pip
11+
12+
# The package causes a conflict with moto
13+
pip uninstall python-lambda
14+
15+
pip install -U -e .
16+
pip install -U -r requirements/testing.txt
17+
pip install -U -r requirements/adapter.txt
18+
pip install -U -r requirements/adapter_testing.txt
19+
pip install -U -r requirements/tools.txt
20+
21+
# To avoid errors due to the old versions of click forced by Chalice
22+
pip install -U pip click

scripts/install_all_and_run_tests.sh

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,19 @@
55

66
script_dir=`dirname $0`
77
cd ${script_dir}/..
8-
rm -rf ./slack_bolt.egg-info
98

10-
# Update pip to prevent warnings
11-
pip install -U pip
9+
test_target="${1:-tests/}"
1210

13-
# The package causes a conflict with moto
14-
pip uninstall python-lambda
11+
# keep in sync with LATEST_SUPPORTED_PY in .github/workflows/ci-build.yml
12+
LATEST_SUPPORTED_PY="3.14"
13+
current_py=$(python --version | sed -E 's/Python ([0-9]+\.[0-9]+).*/\1/')
1514

16-
test_target="$1"
15+
./scripts/install.sh
1716

18-
pip install -U -e .
19-
pip install -U -r requirements/testing.txt
20-
pip install -U -r requirements/adapter.txt
21-
pip install -U -r requirements/adapter_testing.txt
22-
pip install -U -r requirements/tools.txt
23-
# To avoid errors due to the old versions of click forced by Chalice
24-
pip install -U pip click
17+
./scripts/format.sh --no-install
18+
./scripts/lint.sh --no-install
19+
pytest $test_target
2520

26-
if [[ $test_target != "" ]]
27-
then
28-
./scripts/format.sh --no-install
29-
pytest $1
30-
else
31-
./scripts/format.sh --no-install
32-
./scripts/lint.sh --no-install
33-
pytest
21+
if [[ "$current_py" == "$LATEST_SUPPORTED_PY" ]]; then
3422
./scripts/run_mypy.sh --no-install
3523
fi

0 commit comments

Comments
 (0)