Skip to content

Commit f1661fc

Browse files
committed
Add AGENTS.md
1 parent 4e0e38c commit f1661fc

1 file changed

Lines changed: 355 additions & 0 deletions

File tree

AGENTS.md

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

Comments
 (0)