Skip to content
Draft
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
24 changes: 11 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ Each extension is a **separate PyPI package** with its own `setup.cfg`, `setup.p

## Examples (integration test suite)

The `examples/` directory serves as both user-facing documentation and the project's integration test suite. Examples are validated in CI using [mechanical-markdown](https://pypi.org/project/mechanical-markdown/), which executes bash code blocks from README files and asserts expected output.
The `examples/` directory serves as both user-facing documentation and the project's integration test suite. Examples are validated by pytest-based integration tests in `tests/integration/`.

**See `examples/AGENTS.md`** for the full guide on example structure, validation, mechanical-markdown STEP blocks, and how to add new examples.
**See `examples/AGENTS.md`** for the full guide on example structure and how to add new examples.

Quick reference:
```bash
tox -e examples # Run all examples (needs Dapr runtime)
tox -e example-component -- state_store # Run a single example
cd examples && ./validate.sh state_store # Run directly
tox -e integration # Run all examples (needs Dapr runtime)
tox -e integration -- test_state_store.py # Run a single example
```

## Python version support
Expand Down Expand Up @@ -107,8 +106,8 @@ tox -e ruff
# Run type checking
tox -e type

# Validate examples (requires Dapr runtime)
tox -e examples
# Run integration tests / validate examples (requires Dapr runtime)
tox -e integration
```

To run tests directly without tox:
Expand Down Expand Up @@ -190,9 +189,8 @@ When completing any task on this project, work through this checklist. Not every
### Examples (integration tests)

- [ ] If you added a new user-facing feature or building block, add or update an example in `examples/`
- [ ] Ensure the example README has `<!-- STEP -->` blocks with `expected_stdout_lines` so it is validated in CI
- [ ] If you added a new example, register it in `tox.ini` under `[testenv:examples]`
- [ ] If you changed output format of existing functionality, update `expected_stdout_lines` in affected example READMEs
- [ ] Add a corresponding pytest integration test in `tests/integration/`
- [ ] If you changed output format of existing functionality, update expected output in the affected integration tests
- [ ] See `examples/AGENTS.md` for full details on writing examples

### Documentation
Expand All @@ -204,7 +202,7 @@ When completing any task on this project, work through this checklist. Not every

- [ ] Run `tox -e ruff` — linting must be clean
- [ ] Run `tox -e py311` (or your Python version) — all unit tests must pass
- [ ] If you touched examples: `tox -e example-component -- <example-name>` to validate locally
- [ ] If you touched examples: `tox -e integration -- test_<example-name>.py` to validate locally
- [ ] Commits must be signed off for DCO: `git commit -s`

## Important files
Expand All @@ -219,7 +217,7 @@ When completing any task on this project, work through this checklist. Not every
| `dev-requirements.txt` | Development/test dependencies |
| `dapr/version/__init__.py` | SDK version string |
| `ext/*/setup.cfg` | Extension package metadata and dependencies |
| `examples/validate.sh` | Entry point for mechanical-markdown example validation |
| `tests/integration/` | Pytest-based integration tests that validate examples |

## Gotchas

Expand All @@ -228,6 +226,6 @@ When completing any task on this project, work through this checklist. Not every
- **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.
- **DCO signoff**: PRs will be blocked by the DCO bot if commits lack `Signed-off-by`. Always use `git commit -s`.
- **Ruff version pinned**: Dev requirements pin `ruff === 0.14.1`. Use this exact version to match CI.
- **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.
- **Examples are integration tests**: Changing output format (log messages, print statements) can break integration tests. Always check expected output in `tests/integration/` when modifying user-visible output.
- **Background processes in examples**: Examples that start background services (servers, subscribers) must include a cleanup step to stop them, or CI will hang.
- **Workflow is the most active area**: See `ext/dapr-ext-workflow/AGENTS.md` for workflow-specific architecture and constraints.
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
@AGENTS.md

Use pathlib instead of os.path.
Use modern Python (3.10+) features.
Make all code strongly typed.
Keep conditional nesting to a minimum, and use guard clauses when possible.
Aim for medium "visual complexity": use intermediate variables to store results of nested/complex function calls, but don't create a new variable for everything.
Avoid comments unless there is an unusual gotcha, a complex algorithm or anything an experienced code reviewer needs to be aware of. Focus on making better Google-style docstrings instead.

The user is not always right. Be skeptical and do not blindly comply if something doesn't make sense.
Code should be production-ready.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,17 @@ tox -e py311
tox -e type
```

8. Run examples
8. Run integration tests (validates the examples)

```bash
tox -e examples
tox -e integration
```

[Dapr Mechanical Markdown](https://github.com/dapr/mechanical-markdown) is used to test the examples.

If you need to run the examples against a pre-released version of the runtime, you can use the following command:
- Get your daprd runtime binary from [here](https://github.com/dapr/dapr/releases) for your platform.
- Copy the binary to your dapr home folder at $HOME/.dapr/bin/daprd.
Or using dapr cli directly: `dapr init --runtime-version <release version>`
- Now you can run the example with `tox -e examples`.
- Now you can run the examples with `tox -e integration`.


## Documentation
Expand Down
136 changes: 17 additions & 119 deletions examples/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
# AGENTS.md — Dapr Python SDK Examples

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.
The `examples/` directory serves as both **user-facing documentation** and the project's **integration test suite**. Each example is a self-contained application validated by pytest-based integration tests in `tests/integration/`.

## How validation works

1. `examples/validate.sh` is the entry point — it `cd`s into an example directory and runs `mm.py -l README.md`
2. `mm.py` (mechanical-markdown) parses `<!-- STEP -->` HTML comment blocks in the README
3. Each STEP block wraps a fenced bash code block that gets executed
4. stdout/stderr is captured and checked against `expected_stdout_lines` / `expected_stderr_lines`
5. Validation fails if any expected output line is missing
1. Each example has a corresponding test file in `tests/integration/` (e.g., `test_state_store.py`)
2. Tests use a `DaprRunner` helper (defined in `conftest.py`) that wraps `dapr run` commands
3. `DaprRunner.run()` executes a command and captures stdout; `DaprRunner.start()`/`stop()` manage background services
4. Tests assert that expected output lines appear in the captured output

Run examples locally (requires a running Dapr runtime via `dapr init`):

```bash
# All examples
tox -e examples
tox -e integration

# Single example
tox -e example-component -- state_store

# Or directly
cd examples && ./validate.sh state_store
tox -e integration -- test_state_store.py
```

In CI (`validate_examples.yaml`), examples run on all supported Python versions (3.10-3.14) on Ubuntu with a full Dapr runtime including Docker, Redis, and (for LLM examples) Ollama.
Expand All @@ -31,7 +27,7 @@ Each example follows this pattern:

```
examples/<example-name>/
├── README.md # Documentation + mechanical-markdown STEP blocks (REQUIRED)
├── README.md # Documentation (REQUIRED)
├── *.py # Python application files
├── requirements.txt # Dependencies (optional — many examples rely on the installed SDK)
├── components/ # Dapr component YAML configs (if needed)
Expand All @@ -46,53 +42,6 @@ Common Python file naming conventions:
- Client/caller side: `*-caller.py`, `publisher.py`, `*_client.py`
- Standalone: `state_store.py`, `crypto.py`, etc.

## Mechanical-markdown STEP block format

STEP blocks are HTML comments wrapping fenced bash code in the README:

````markdown
<!-- STEP
name: Run the example
expected_stdout_lines:
- '== APP == Got state: value'
- '== APP == State deleted'
background: false
sleep: 5
timeout_seconds: 30
output_match_mode: substring
match_order: none
-->

```bash
dapr run --app-id myapp --resources-path ./components/ python3 example.py
```

<!-- END_STEP -->
````

### STEP block attributes

| Attribute | Description |
|-----------|-------------|
| `name` | Descriptive name for the step |
| `expected_stdout_lines` | List of strings that must appear in stdout |
| `expected_stderr_lines` | List of strings that must appear in stderr |
| `background` | `true` to run in background (for long-running services) |
| `sleep` | Seconds to wait after starting before moving to the next step |
| `timeout_seconds` | Max seconds before the step is killed |
| `output_match_mode` | `substring` for partial matching (default is exact) |
| `match_order` | `none` if output lines can appear in any order |

### Tips for writing STEP blocks

- Use `background: true` with `sleep:` for services that need to stay running (servers, subscribers)
- Use `timeout_seconds:` to prevent CI hangs on broken examples
- Use `output_match_mode: substring` when output contains timestamps or dynamic content
- Use `match_order: none` when multiple concurrent operations produce unpredictable ordering
- Always include a cleanup step (e.g., `dapr stop --app-id ...`) when using background processes
- Make `expected_stdout_lines` specific enough to validate correctness, but not so brittle they break on cosmetic changes
- Dapr prefixes app output with `== APP ==` — use this in expected lines

## Dapr component YAML format

Components in `components/` directories follow the standard Dapr resource format:
Expand Down Expand Up @@ -182,69 +131,18 @@ The `workflow` example includes: `simple.py`, `task_chaining.py`, `fan_out_fan_i
1. Create a directory under `examples/` with a descriptive kebab-case name
2. Add Python source files and a `requirements.txt` referencing the needed SDK packages
3. Add Dapr component YAMLs in a `components/` subdirectory if the example uses state, pubsub, etc.
4. Write a `README.md` with:
- Introduction explaining what the example demonstrates
- Pre-requisites section (Dapr CLI, Python 3.10+, any special tools)
- Install instructions (`pip3 install dapr dapr-ext-grpc` etc.)
- Running instructions with `<!-- STEP -->` blocks wrapping `dapr run` commands
- Expected output section
- Cleanup step to stop background processes
5. Register the example in `tox.ini` under `[testenv:examples]` commands:
```
./validate.sh your-example-name
```
6. Test locally: `cd examples && ./validate.sh your-example-name`

## Common README template

```markdown
# Dapr [Building Block] Example

This example demonstrates how to use the Dapr [building block] API with the Python SDK.

## Pre-requisites

- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started)
- Python 3.10+

## Install Dapr python-SDK

\`\`\`bash
pip3 install dapr dapr-ext-grpc
\`\`\`

## Run the example

<!-- STEP
name: Run example
expected_stdout_lines:
- '== APP == Expected output here'
timeout_seconds: 30
-->

\`\`\`bash
dapr run --app-id myapp --resources-path ./components/ python3 example.py
\`\`\`

<!-- END_STEP -->

## Cleanup

<!-- STEP
name: Cleanup
-->

\`\`\`bash
dapr stop --app-id myapp
\`\`\`

<!-- END_STEP -->
```
4. Write a `README.md` with introduction, pre-requisites, install instructions, and running instructions
5. Add a corresponding test in `tests/integration/test_<example_name>.py`:
- Use the `@pytest.mark.example_dir('<example-name>')` marker to set the working directory
- Use `dapr.run()` for scripts that exit on their own, `dapr.start()`/`dapr.stop()` for long-running services
- Assert expected output lines appear in the captured output
6. Test locally: `tox -e integration -- test_<example_name>.py`

## Gotchas

- **Output format changes break CI**: If you modify print statements or log output in SDK code, check whether any example's `expected_stdout_lines` depend on that output.
- **Background processes must be cleaned up**: Missing cleanup steps cause CI to hang.
- **Output format changes break tests**: If you modify print statements or log output in SDK code, check whether any integration test's expected lines depend on that output.
- **Background processes must be cleaned up**: The `DaprRunner` fixture handles cleanup on teardown, but tests should still call `dapr.stop()` to capture output.
- **Dapr prefixes output**: Application stdout appears as `== APP == <line>` when run via `dapr run`.
- **Redis is available in CI**: The CI environment has Redis running on `localhost:6379` — most component YAMLs use this.
- **Some examples need special setup**: `crypto` generates keys, `configuration` seeds Redis, `conversation` needs LLM config — check individual READMEs.
- **Infinite-loop example scripts**: Some example scripts (e.g., `invoke-caller.py`) have `while True` loops for demo purposes. Integration tests must either bypass these with HTTP API calls or use `dapr.run(until=...)` for early termination.
4 changes: 0 additions & 4 deletions examples/validate.sh

This file was deleted.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ ignore = ["E501","E203", "E712", "E722", "E713"]

[tool.ruff.format]
quote-style = 'single'

[tool.pytest.ini_options]
markers = [
'example_dir(name): set the example directory for the dapr fixture',
]
Loading
Loading