Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
65fc9ae
Remove Mechanical Markdown
seherv Apr 10, 2026
f2cb189
Address Copilot comments
seherv Apr 13, 2026
a798922
Address Copilot comments (2)
seherv Apr 13, 2026
93cbe59
Make Langgraph test runnable in local without API keys
seherv Apr 14, 2026
f7b9318
Address Copilot comments (3)
seherv Apr 14, 2026
1a11546
Merge branch 'main' into remove-mechanical-markdown
sicoyle Apr 14, 2026
b1a74f4
Run ruff format
seherv Apr 15, 2026
71e4311
Run redis-cli flushdb from container
seherv Apr 15, 2026
0a2f7a2
Add missing test_invoke_http.py
seherv Apr 15, 2026
074b1dc
Address Copilot comments (4)
seherv Apr 15, 2026
6f26d12
Merge branch 'main' into remove-mechanical-markdown
seherv Apr 16, 2026
b074579
Editable install for examples/ validation
seherv Apr 17, 2026
a4912dc
Add timeout to
seherv Apr 20, 2026
e200d3e
Address Copilot comments (4)
seherv Apr 20, 2026
0617f35
Remove all subprocess(shell=True) calls
seherv Apr 20, 2026
317e0df
Merge branch 'main' into remove-mechanical-markdown
sicoyle Apr 21, 2026
3fcd2ac
Address PR feedback
seherv Apr 21, 2026
3f3ca50
Merge remote-tracking branch 'origin/remove-mechanical-markdown' into…
seherv Apr 21, 2026
2766a45
Use static crypto keys
seherv Apr 21, 2026
f01a7fd
Add missing -- separators
seherv Apr 21, 2026
a752fde
Add missing -- separators to examples/ READMEs
seherv Apr 21, 2026
260b30f
Address PR comments (2)
seherv Apr 21, 2026
4e458b1
Update validate_examples.yaml
seherv Apr 21, 2026
fefd619
Merge branch 'main' into remove-mechanical-markdown
seherv Apr 21, 2026
53ddae8
Run python3 in test_grpc_proxying.py
seherv Apr 22, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/validate_examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,6 @@ jobs:
nohup ollama serve &
sleep 10
ollama pull llama3.2:latest
- name: Check Examples
- name: Check examples
run: |
tox -e examples
tox -e integration
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.
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
@AGENTS.md

Use pathlib instead of os.path.
Use httpx instead of urllib.
subprocess(`shell=True`) is used only when it makes the code more readable. Use either shlex or args lists.
subprocess calls should have a reasonable timeout.
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 a 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 cross-platform and 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.
52 changes: 52 additions & 0 deletions examples/crypto/keys/rsa-private-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
VfTYN1T7nfugAi8VqPcL/5ZKQzIz
-----END PRIVATE KEY-----
1 change: 1 addition & 0 deletions examples/crypto/keys/symmetric-key-256
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sÔßsŸÁÆ¿kU>A@{0ÕûZJýlÀ“CQõ
2 changes: 1 addition & 1 deletion examples/demo_actor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ expected_stdout_lines:
```bash
# Run actor client
cd demo_actor
dapr run --app-id demo-client python3 demo_actor_client.py
dapr run --app-id demo-client -- python3 demo_actor_client.py
```

Expected output:
Expand Down
2 changes: 1 addition & 1 deletion examples/demo_actor/demo_actor/demo_actor_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def main():
# non-remoting actor invocation
print('call actor method via proxy.invoke_method()', flush=True)
rtn_bytes = await proxy.invoke_method('GetMyData')
print(rtn_bytes, flush=True)
print(rtn_bytes.decode(), flush=True)
# RPC style using python duck-typing
print('call actor method using rpc style', flush=True)
rtn_obj = await proxy.GetMyData()
Expand Down
2 changes: 1 addition & 1 deletion examples/distributed_lock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ timeout_seconds: 5
-->

```bash
dapr run --app-id=locksapp --app-protocol grpc --resources-path components/ python3 lock.py
dapr run --app-id=locksapp --app-protocol grpc --resources-path components/ -- python3 lock.py
```
<!-- END_STEP -->

Expand Down
2 changes: 1 addition & 1 deletion examples/distributed_lock/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def main():
print('Will try to acquire a lock from lock store named [%s]' % store_name)
print('The lock is for a resource named [%s]' % resource_id)
print('The client identifier is [%s]' % client_id)
print('The lock will will expire in %s seconds.' % expiry_in_seconds)
print('The lock will expire in %s seconds.' % expiry_in_seconds)

with dapr.try_lock(store_name, resource_id, client_id, expiry_in_seconds) as lock_result:
assert lock_result.success, 'Failed to acquire the lock. Aborting.'
Expand Down
Loading
Loading