trio — Cursor rule for Python: guidance and installation
This Cursor rule guides agents working with Trio-based asynchronous Python, from task lifetimes and cancellation to typing and tests. Use it in projects with Trio code.
.cursor/rules/trio.mdcWhat trio does
The rule targets **/*.py files. It steers the agent toward trio.open_nursery() for concurrent tasks, explicit cancellation handling, and keeping synchronous calls out of the event loop. For blocking functions, it demonstrates trio.to_thread.run_sync.
It also covers type annotations, checks with mypy --strict and flake8-async or Ruff’s ASYNC rules, plus tests using trio.testing. For code meant to run across async frameworks, it recommends AnyIO primitives. A synchronous I/O call inside async def holds up other tasks until that call returns.
What it helps with
- manage concurrent task lifetimes with nurseries
- handle cancellation and clean up resources
- move blocking calls out of the event loop
- add type hints to async functions
- test Trio code with
trio.testing
Example prompts for the agent
- Refactor this function so its tasks run inside
trio.open_nursery(). - Add cancellation handling and resource cleanup to this Trio operation.
- Check this async code for blocking calls and add suitable type hints.
How to install trio
Manually: put the files in place
.cursor/rules/trio.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/trio.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/trio.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/trio.mdManually: put the files in place
.clinerules/trio.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about trio
How do I install trio in Cursor?
Add the rule file to your project at .cursor/rules/trio.mdc.
What does the trio rule do?
It guides agents writing asynchronous Python with Trio, covering structured task management, cancellation, non-blocking I/O, typing, and tests.
How does the rule handle blocking calls?
It shows trio.to_thread.run_sync for running a synchronous function in a separate thread. For network I/O, it lists trio.socket, trio-http-client, and httpx with the Trio backend.
Source text
trio.mdc · CC0-1.0
---
description: This guide enforces best practices for using Trio, Python's structured concurrency library, ensuring robust, type-safe, and performant asynchronous applications.
globs: **/*.py
---
# trio Best Practices
Trio is the definitive choice for new async-first Python projects in 2025, prioritizing usability and correctness through structured concurrency. Adhere to these guidelines for maintainable, high-performance code.
## 1. Structured Concurrency with Nurseries
Always manage concurrent tasks within a `trio.open_nursery()`. This guarantees that all spawned tasks complete or are cancelled together, preventing hidden task leaks and simplifying error handling.
❌ **BAD: Fire-and-forget tasks**
```python
import trio
async def background_task():
await trio.sleep(100) # This task will run indefinitely, unmanaged
async def main():
# No nursery, no explicit management
trio.lowlevel.spawn_system_task(background_task)
print("Main task exiting, background task is orphaned.")
trio.run(main)
```
✅ **GOOD: Tasks within a nursery**
```python
import trio
async def managed_task(task_id: int):
print(f"Task {task_id} started.")
await trio.sleep(1)
print(f"Task {task_id} finished.")
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(managed_task, 1)
nursery.start_soon(managed_task, 2)
print("All tasks in nursery completed.")
trio.run(main)
```
## 2. Explicit Cancellation and Timeouts
Trio's cancellation model is robust. Use `cancel_scope` for explicit control over task lifetimes and `trio.Cancelled` for graceful cleanup.
❌ **BAD: Ignoring cancellation or using raw `try...except`**
```python
import trio
async def long_running_operation():
try:
await trio.sleep(10)
print("Operation completed.")
except Exception: # Too broad, doesn't specifically handle cancellation
print("Operation failed somehow.")
async def main():
with trio.move_on_after(0.1) as cancel_scope:
await long_running_operation()
if cancel_scope.cancelled_caught:
print("Operation was cancelled.")
trio.run(main)
```
✅ **GOOD: Handling `trio.Cancelled` for cleanup**
```python
import trio
async def long_running_operation():
try:
print("Starting long operation...")
await trio.sleep(10)
print("Operation completed.")
except trio.Cancelled:
print("Operation was cancelled, performing cleanup.")
# Perform necessary cleanup here
raise # Re-raise to propagate cancellation
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(long_running_operation)
await trio.sleep(0.1)
nursery.cancel_scope.cancel() # Explicitly cancel the nursery
trio.run(main)
```
## 3. I/O Hygiene: Never Block the Event Loop
Blocking calls (e.g., `time.sleep()`, synchronous file I/O, `requests.get()`) will starve the event loop and halt all other concurrent tasks. Use Trio-aware alternatives or offload to a thread.
❌ **BAD: Blocking call in an `async def` function**
```python
import time
import trio
async def fetch_data_blocking():
print("Fetching data (blocking)...")
time.sleep(2) # Blocks the entire event loop!
print("Data fetched.")
return {"data": "example"}
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(fetch_data_blocking)
nursery.start_soon(fetch_data_blocking) # These will run sequentially
trio.run(main)
```
✅ **GOOD: Offloading blocking calls with `trio.to_thread.run_sync`**
```python
import time
import trio
def _blocking_fetch_data(): # This is a regular sync function
print("Fetching data (blocking in thread)...")
time.sleep(2)
print("Data fetched in thread.")
return {"data": "example"}
async def fetch_data_non_blocking():
return await trio.to_thread.run_sync(_blocking_fetch_data)
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(fetch_data_non_blocking)
nursery.start_soon(fetch_data_non_blocking) # These will run concurrently
trio.run(main)
```
For network I/O, use `trio.socket`, `trio-http-client`, or `httpx` (with `trio` backend).
## 4. Type Safety and Linting
Mandate type hints for all `async def` functions, variables, and return types. Use `mypy --strict` and `flake8-async` (or Ruff with `ASYNC` rules) to catch common async-related errors early.
❌ **BAD: Missing type hints, potential runtime errors**
```python
import trio
async def process_item(item): # 'item' has no type hint
await trio.sleep(0.1)
return item * 2
async def main():
result = await process_item("text") # Will fail at runtime if 'item' is str
print(result)
trio.run(main)
```
✅ **GOOD: Comprehensive type hints**
```python
import trio
async def process_item(item: int) -> int:
await trio.sleep(0.1)
return item * 2
async def main():
result = await process_item(5) # Type checker will catch incorrect usage
print(result)
trio.run(main)
```
Configure `ruff` to enable `ASYNC` rules (e.g., `select = ["E", "F", "ASYNC"]` in `pyproject.toml`).
## 5. Interoperability with AnyIO
When building libraries or applications that need to be framework-agnostic (e.g., compatible with FastAPI, which uses AnyIO), write your async code using AnyIO primitives. This keeps your codebase portable across Trio, asyncio, and Curio.
```python
# Using AnyIO for portable async code
import anyio
async def do_something_concurrently():
async with anyio.create_task_group() as tg:
tg.start_soon(anyio.sleep, 1)
tg.start_soon(anyio.sleep, 2)
print("AnyIO tasks completed.")
async def main():
await do_something_concurrently()
# Run with Trio backend
anyio.run(main, backend="trio")
```
## 6. Testing Approaches
Leverage `trio.testing` for reliable and deterministic tests of your asynchronous code.
```python
import trio
import trio.testing
import pytest
async def worker(value: int) -> int:
await trio.sleep(0.01)
return value * 2
@pytest.mark.trio
async def test_worker_doubles_value():
# Use trio.testing.wait_all_tasks_blocked() for deterministic execution
async with trio.open_nursery() as nursery:
send_channel, receive_channel = trio.open_memory_channel(0)
nursery.start_soon(lambda: nursery.start_soon(worker, 5)) # Example task
await trio.testing.wait_all_tasks_blocked() # Ensures tasks yield
# Assertions about task state or results
assert await worker(3) == 6
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc