|
| 1 | +# AGENTS.md — Dapr Python SDK |
| 2 | + |
| 3 | +This file provides context for AI agents working on the Dapr Python SDK. |
| 4 | +The project is the official Python SDK for [Dapr](https://dapr.io/) (Distributed Application Runtime), |
| 5 | +enabling Python developers to build distributed applications using Dapr building blocks. |
| 6 | + |
| 7 | +Repository: https://github.com/dapr/python-sdk |
| 8 | +License: Apache 2.0 |
| 9 | + |
| 10 | +## Project structure |
| 11 | + |
| 12 | +``` |
| 13 | +dapr/ # Core SDK package |
| 14 | +├── actor/ # Actor framework (virtual actor model) |
| 15 | +├── aio/ # Async I/O modules |
| 16 | +├── clients/ # Dapr clients (gRPC and HTTP) |
| 17 | +├── common/ # Shared utilities |
| 18 | +├── conf/ # Configuration (settings, environment) |
| 19 | +├── proto/ # Auto-generated gRPC protobuf stubs (DO NOT EDIT) |
| 20 | +├── serializers/ # JSON and pluggable serializers |
| 21 | +└── version/ # Version metadata |
| 22 | +
|
| 23 | +ext/ # Extension packages (each is a separate PyPI package) |
| 24 | +├── dapr-ext-grpc/ # gRPC App extension |
| 25 | +├── dapr-ext-fastapi/ # FastAPI integration |
| 26 | +├── dapr-ext-workflow/ # Workflow authoring |
| 27 | +├── dapr-ext-langgraph/ # LangGraph checkpointer |
| 28 | +├── dapr-ext-strands/ # Strands agent extension |
| 29 | +└── flask_dapr/ # Flask integration |
| 30 | +
|
| 31 | +tests/ # Unit tests (mirrors dapr/ package structure) |
| 32 | +examples/ # Example applications (each subfolder is self-contained) |
| 33 | +docs/ # Sphinx documentation source |
| 34 | +tools/ # Build and release scripts |
| 35 | +``` |
| 36 | + |
| 37 | +## Key architectural patterns |
| 38 | + |
| 39 | +- **Namespace packages**: The `dapr` namespace is shared across the core SDK and extensions using `find_namespace_packages`. Extensions live in `ext/` but install into the `dapr.ext.*` namespace. |
| 40 | +- **Client architecture**: `DaprGrpcClient` (primary, high-performance) and HTTP-based clients. Both implement shared interfaces. |
| 41 | +- **Actor model**: `Actor` base class, `ActorInterface` with `@actormethod` decorator, `ActorProxy`/`ActorProxyFactory` for client-side references, `ActorRuntime` for server-side hosting. |
| 42 | +- **Serialization**: Pluggable via `Serializer` base class. `DefaultJSONSerializer` is the default. |
| 43 | +- **Proto files**: Auto-generated from Dapr proto definitions. Never edit files under `dapr/proto/` directly. |
| 44 | + |
| 45 | +## Python version support |
| 46 | + |
| 47 | +- **Minimum**: Python 3.10 |
| 48 | +- **Tested**: 3.10, 3.11, 3.12, 3.13, 3.14 |
| 49 | +- **Target version for tooling**: `py310` (ruff, mypy) |
| 50 | + |
| 51 | +## Development setup |
| 52 | + |
| 53 | +Install all packages in editable mode with dev dependencies: |
| 54 | + |
| 55 | +```bash |
| 56 | +pip install -r dev-requirements.txt \ |
| 57 | + -e . \ |
| 58 | + -e ext/dapr-ext-workflow/ \ |
| 59 | + -e ext/dapr-ext-grpc/ \ |
| 60 | + -e ext/dapr-ext-fastapi/ \ |
| 61 | + -e ext/dapr-ext-langgraph/ \ |
| 62 | + -e ext/dapr-ext-strands/ \ |
| 63 | + -e ext/flask_dapr/ |
| 64 | +``` |
| 65 | + |
| 66 | +## Running tests |
| 67 | + |
| 68 | +Tests use Python's built-in `unittest` framework with `coverage`. Run via tox: |
| 69 | + |
| 70 | +```bash |
| 71 | +# Run unit tests (replace 311 with your Python version) |
| 72 | +tox -e py311 |
| 73 | + |
| 74 | +# Run linting and formatting |
| 75 | +tox -e ruff |
| 76 | + |
| 77 | +# Run type checking |
| 78 | +tox -e type |
| 79 | + |
| 80 | +# Validate examples (requires Dapr runtime) |
| 81 | +tox -e examples |
| 82 | +``` |
| 83 | + |
| 84 | +To run tests directly without tox: |
| 85 | + |
| 86 | +```bash |
| 87 | +# Core SDK tests |
| 88 | +python -m unittest discover -v ./tests |
| 89 | + |
| 90 | +# Extension tests (run each separately) |
| 91 | +python -m unittest discover -v ./ext/dapr-ext-workflow/tests |
| 92 | +python -m unittest discover -v ./ext/dapr-ext-grpc/tests |
| 93 | +python -m unittest discover -v ./ext/dapr-ext-fastapi/tests |
| 94 | +python -m unittest discover -v ./ext/dapr-ext-langgraph/tests |
| 95 | +python -m unittest discover -v ./ext/dapr-ext-strands/tests |
| 96 | +python -m unittest discover -v ./ext/flask_dapr/tests |
| 97 | +``` |
| 98 | + |
| 99 | +## Code style and linting |
| 100 | + |
| 101 | +**Formatter/Linter**: Ruff (v0.14.1) |
| 102 | + |
| 103 | +Key rules: |
| 104 | +- **Line length**: 100 characters (E501 is currently ignored, but respect the 100-char target) |
| 105 | +- **Quote style**: Single quotes |
| 106 | +- **Import sorting**: isort-compatible (ruff `I` rules) |
| 107 | +- **Target**: Python 3.10 |
| 108 | +- **Excluded from linting**: `.github/`, `dapr/proto/` |
| 109 | + |
| 110 | +Run formatting and lint fixes: |
| 111 | + |
| 112 | +```bash |
| 113 | +ruff check --fix |
| 114 | +ruff format |
| 115 | +``` |
| 116 | + |
| 117 | +**Type checking**: MyPy |
| 118 | + |
| 119 | +```bash |
| 120 | +mypy --config-file mypy.ini |
| 121 | +``` |
| 122 | + |
| 123 | +MyPy is configured to check: `dapr/actor/`, `dapr/clients/`, `dapr/conf/`, `dapr/serializers/`, `ext/dapr-ext-grpc/`, `ext/dapr-ext-fastapi/`, `ext/flask_dapr/`, and `examples/demo_actor/`. Proto stubs (`dapr.proto.*`) have errors ignored. |
| 124 | + |
| 125 | +## Commit and PR conventions |
| 126 | + |
| 127 | +- **DCO required**: Every commit must include a `Signed-off-by` line. Use `git commit -s` to add it automatically. |
| 128 | +- **CI checks**: Linting (ruff), unit tests (Python 3.10-3.14), type checking (mypy), and DCO verification run on all PRs. |
| 129 | +- **Branch targets**: PRs go to `main` or `release-*` branches. |
| 130 | +- **Tag-based releases**: Tags like `v*`, `workflow-v*`, `grpc-v*`, `fastapi-v*`, `flask-v*`, `langgraph-v*`, `strands-v*` trigger PyPI publishing for the corresponding package. |
| 131 | + |
| 132 | +## Examples (integration test suite) |
| 133 | + |
| 134 | +The `examples/` directory serves as both user-facing documentation and the project's **integration test suite**. Each example is a self-contained application validated automatically in CI using [mechanical-markdown](https://pypi.org/project/mechanical-markdown/), which executes bash code blocks embedded in README files and asserts expected output. |
| 135 | + |
| 136 | +### How example validation works |
| 137 | + |
| 138 | +1. `examples/validate.sh` is the entry point — it `cd`s into an example directory and runs `mm.py -l README.md` |
| 139 | +2. `mm.py` (mechanical-markdown) parses `<!-- STEP -->` HTML comment blocks in the README |
| 140 | +3. Each STEP block wraps a bash code fence that gets executed |
| 141 | +4. stdout/stderr is captured and checked against `expected_stdout_lines` / `expected_stderr_lines` |
| 142 | +5. Validation fails if any expected output line is missing |
| 143 | + |
| 144 | +Run examples locally (requires a running Dapr runtime via `dapr init`): |
| 145 | + |
| 146 | +```bash |
| 147 | +# All examples |
| 148 | +tox -e examples |
| 149 | + |
| 150 | +# Single example |
| 151 | +tox -e example-component -- state_store |
| 152 | + |
| 153 | +# Or directly |
| 154 | +cd examples && ./validate.sh state_store |
| 155 | +``` |
| 156 | + |
| 157 | +In CI (`validate_examples.yaml`), examples run against all supported Python versions (3.10-3.14) on Ubuntu with a full Dapr runtime environment including Docker, Redis, and (for LLM examples) Ollama. |
| 158 | + |
| 159 | +### Example directory structure |
| 160 | + |
| 161 | +Each example follows this pattern: |
| 162 | + |
| 163 | +``` |
| 164 | +examples/<example-name>/ |
| 165 | +├── README.md # Documentation + mechanical-markdown STEP blocks (REQUIRED) |
| 166 | +├── *.py # Python application files |
| 167 | +├── requirements.txt # Dependencies (e.g., dapr>=1.17.0rc6) |
| 168 | +├── components/ # Dapr component YAML configs (if needed) |
| 169 | +│ ├── statestore.yaml |
| 170 | +│ └── pubsub.yaml |
| 171 | +├── config.yaml # Dapr configuration (optional, e.g., for tracing/features) |
| 172 | +└── proto/ # Protobuf definitions (for gRPC examples) |
| 173 | +``` |
| 174 | + |
| 175 | +Common Python file naming conventions: |
| 176 | +- Server/receiver side: `*-receiver.py`, `subscriber.py`, `*_service.py` |
| 177 | +- Client/caller side: `*-caller.py`, `publisher.py`, `*_client.py` |
| 178 | +- Standalone: `state_store.py`, `crypto.py`, etc. |
| 179 | + |
| 180 | +### Mechanical-markdown STEP block format |
| 181 | + |
| 182 | +STEP blocks are HTML comments wrapping fenced bash code in the README: |
| 183 | + |
| 184 | +````markdown |
| 185 | +<!-- STEP |
| 186 | +name: Run the example |
| 187 | +expected_stdout_lines: |
| 188 | + - '== APP == Got state: value' |
| 189 | + - '== APP == State deleted' |
| 190 | +background: false |
| 191 | +sleep: 5 |
| 192 | +timeout_seconds: 30 |
| 193 | +output_match_mode: substring |
| 194 | +match_order: none |
| 195 | +--> |
| 196 | + |
| 197 | +```bash |
| 198 | +dapr run --app-id myapp --resources-path ./components/ python3 example.py |
| 199 | +``` |
| 200 | + |
| 201 | +<!-- END_STEP --> |
| 202 | +```` |
| 203 | + |
| 204 | +**STEP block attributes:** |
| 205 | + |
| 206 | +| Attribute | Description | |
| 207 | +|-----------|-------------| |
| 208 | +| `name` | Descriptive name for the step | |
| 209 | +| `expected_stdout_lines` | List of strings that must appear in stdout | |
| 210 | +| `expected_stderr_lines` | List of strings that must appear in stderr | |
| 211 | +| `background` | `true` to run in background (for long-running services) | |
| 212 | +| `sleep` | Seconds to wait after starting before moving to the next step | |
| 213 | +| `timeout_seconds` | Max seconds before the step is killed | |
| 214 | +| `output_match_mode` | `substring` for partial matching (default is exact) | |
| 215 | +| `match_order` | `none` if output lines can appear in any order | |
| 216 | + |
| 217 | +**Tips for writing STEP blocks:** |
| 218 | +- Use `background: true` with `sleep:` for services that need to stay running (servers, subscribers) |
| 219 | +- Use `timeout_seconds:` to prevent CI hangs on broken examples |
| 220 | +- Use `output_match_mode: substring` when output contains timestamps or dynamic content |
| 221 | +- Use `match_order: none` when multiple concurrent operations produce unpredictable ordering |
| 222 | +- Always include a cleanup step (e.g., `dapr stop`) when using background processes |
| 223 | +- Make `expected_stdout_lines` specific enough to validate correctness, but not so brittle they break on cosmetic changes |
| 224 | + |
| 225 | +### Dapr component YAML format |
| 226 | + |
| 227 | +Components in `components/` directories follow the standard Dapr resource format: |
| 228 | + |
| 229 | +```yaml |
| 230 | +apiVersion: dapr.io/v1alpha1 |
| 231 | +kind: Component |
| 232 | +metadata: |
| 233 | + name: statestore |
| 234 | +spec: |
| 235 | + type: state.redis |
| 236 | + version: v1 |
| 237 | + metadata: |
| 238 | + - name: redisHost |
| 239 | + value: localhost:6379 |
| 240 | + - name: redisPassword |
| 241 | + value: "" |
| 242 | +``` |
| 243 | +
|
| 244 | +Common component types used in examples: `state.redis`, `pubsub.redis`, `lock.redis`, `configuration.redis`, `crypto.dapr.localstorage`, `bindings.*`. |
| 245 | + |
| 246 | +### Adding a new example |
| 247 | + |
| 248 | +1. Create a directory under `examples/` with a descriptive kebab-case name |
| 249 | +2. Add Python source files and a `requirements.txt` referencing the needed SDK packages |
| 250 | +3. Add Dapr component YAMLs in a `components/` subdirectory if the example uses state, pubsub, etc. |
| 251 | +4. Write a `README.md` with: |
| 252 | + - Introduction explaining what the example demonstrates |
| 253 | + - Pre-requisites section |
| 254 | + - Install instructions |
| 255 | + - Running instructions with `<!-- STEP -->` blocks wrapping the `dapr run` commands |
| 256 | + - Expected output section |
| 257 | + - Cleanup step to stop background processes |
| 258 | +5. Register the example in `tox.ini` under `[testenv:examples]` commands: |
| 259 | + ``` |
| 260 | + ./validate.sh your-example-name |
| 261 | + ``` |
| 262 | +6. Test locally: `cd examples && ./validate.sh your-example-name` |
| 263 | + |
| 264 | +## Common tasks |
| 265 | + |
| 266 | +### Adding a new feature to the core SDK |
| 267 | + |
| 268 | +1. Implement in the appropriate module under `dapr/` |
| 269 | +2. Add unit tests under `tests/` mirroring the package structure |
| 270 | +3. Run `tox -e ruff` to fix formatting |
| 271 | +4. Run `tox -e py311` (or your Python version) to verify tests pass |
| 272 | +5. Run `tox -e type` to check types if you modified typed modules |
| 273 | + |
| 274 | +### Adding or modifying an extension |
| 275 | + |
| 276 | +1. Each extension in `ext/` has its own `setup.cfg`, `setup.py`, and `tests/` directory |
| 277 | +2. The extension installs into the `dapr.ext.*` namespace |
| 278 | +3. Update the extension's `setup.cfg` if adding new dependencies |
| 279 | +4. Add tests under the extension's own `tests/` folder |
| 280 | +5. The extension must be added to `tox.ini` commands if not already present |
| 281 | + |
| 282 | +### Updating proto/gRPC stubs |
| 283 | + |
| 284 | +Files under `dapr/proto/` are auto-generated. Do not edit them directly. Regeneration is handled by build tooling from upstream Dapr proto definitions. |
| 285 | + |
| 286 | +## Agent task checklist |
| 287 | + |
| 288 | +When completing any task on this project, work through this checklist to make sure nothing is missed. Not every item applies to every change — use judgment — but always consider each one. |
| 289 | + |
| 290 | +### Before writing code |
| 291 | + |
| 292 | +- [ ] Read the relevant existing source files before making changes |
| 293 | +- [ ] Understand the existing patterns in the area you're modifying (naming, error handling, async vs sync) |
| 294 | +- [ ] Check if there's both a sync and async variant that needs updating (see `dapr/aio/` and `dapr/clients/http/` for async counterparts) |
| 295 | + |
| 296 | +### Implementation |
| 297 | + |
| 298 | +- [ ] Follow existing code style: single quotes, 100-char lines, Python 3.10+ syntax |
| 299 | +- [ ] Do not edit files under `dapr/proto/` — these are auto-generated |
| 300 | +- [ ] Do not add `__init__.py` files to namespace package roots in extensions |
| 301 | + |
| 302 | +### Unit tests |
| 303 | + |
| 304 | +- [ ] Add or update unit tests under `tests/` (core SDK) or `ext/*/tests/` (extensions) |
| 305 | +- [ ] Tests use `unittest` — follow the existing test patterns in the relevant directory |
| 306 | +- [ ] Verify tests pass: `python -m unittest discover -v ./tests` (or the relevant test directory) |
| 307 | + |
| 308 | +### Linting and type checking |
| 309 | + |
| 310 | +- [ ] Run `ruff check --fix && ruff format` and fix any remaining issues |
| 311 | +- [ ] Run `mypy --config-file mypy.ini` if you changed files covered by mypy (actor, clients, conf, serializers, ext-grpc, ext-fastapi, flask_dapr) |
| 312 | + |
| 313 | +### Examples (integration tests) |
| 314 | + |
| 315 | +- [ ] If you added a new user-facing feature or building block, add or update an example in `examples/` |
| 316 | +- [ ] Ensure the example README has `<!-- STEP -->` blocks with `expected_stdout_lines` so it is validated in CI |
| 317 | +- [ ] If you added a new example, register it in `tox.ini` under `[testenv:examples]` |
| 318 | +- [ ] If you changed output format of existing functionality, update `expected_stdout_lines` in affected example READMEs |
| 319 | + |
| 320 | +### Documentation |
| 321 | + |
| 322 | +- [ ] Update docstrings if you changed a public API's signature or behavior |
| 323 | +- [ ] Update the relevant example README if the usage pattern changed |
| 324 | + |
| 325 | +### Final verification |
| 326 | + |
| 327 | +- [ ] Run `tox -e ruff` — linting must be clean |
| 328 | +- [ ] Run `tox -e py311` (or your Python version) — all unit tests must pass |
| 329 | +- [ ] If you touched examples: `tox -e example-component -- <example-name>` to validate locally |
| 330 | +- [ ] Commits must be signed off for DCO: `git commit -s` |
| 331 | + |
| 332 | +## Important files |
| 333 | + |
| 334 | +| File | Purpose | |
| 335 | +|------|---------| |
| 336 | +| `setup.cfg` | Core package metadata and dependencies | |
| 337 | +| `setup.py` | Package build script (handles dev version suffixing) | |
| 338 | +| `pyproject.toml` | Ruff configuration | |
| 339 | +| `tox.ini` | Test environments and CI commands | |
| 340 | +| `mypy.ini` | Type checking configuration | |
| 341 | +| `dev-requirements.txt` | Development/test dependencies | |
| 342 | +| `dapr/version/__init__.py` | SDK version string | |
| 343 | +| `ext/*/setup.cfg` | Extension package metadata and dependencies | |
| 344 | +| `examples/validate.sh` | Entry point for mechanical-markdown example validation | |
| 345 | +| `examples/*/README.md` | Example docs with embedded integration test steps | |
| 346 | + |
| 347 | +## Gotchas |
| 348 | + |
| 349 | +- **Namespace packages**: Do not add `__init__.py` to the top-level `dapr/` directory in extensions — it will break namespace package resolution. |
| 350 | +- **Proto files**: Never manually edit anything under `dapr/proto/`. These are generated. |
| 351 | +- **Extension independence**: Each extension is a separate PyPI package. Core SDK changes should not break extensions; extension changes should not require core SDK changes unless intentional. |
| 352 | +- **DCO signoff**: PRs will be blocked by the DCO bot if commits lack `Signed-off-by`. Always use `git commit -s`. |
| 353 | +- **Ruff version pinned**: Dev requirements pin `ruff === 0.14.1`. Use this exact version to match CI. |
| 354 | +- **Examples are integration tests**: Changing output format (log messages, print statements) can break example validation. Always check `expected_stdout_lines` in example READMEs when modifying user-visible output. |
| 355 | +- **Background processes in examples**: Examples that start background services (servers, subscribers) must include a cleanup step to stop them, or CI will hang. |
0 commit comments