|
| 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 |
0 commit comments