Migration Guide: asyncio → anyio (3.0.0)
async-btree 3.0.0 replaces asyncio.Runner with anyio as the sole execution backend.
If you were already using the asyncio path (no curio), this guide covers what changed.
Dependency changes
Remove:
pytest-asyncio
Add (for non-asyncio backends, optional):
trio >= 0.26
uvloop >= 0.21
API changes
BTreeRunner
BTreeRunner.__init__ now takes backend instead of disable_curio:
# before
with BTreeRunner() as runner:
runner.run(my_tree) # callable — unchanged
# after
with BTreeRunner() as runner: # asyncio default — no change needed
runner.run(my_tree)
# new: explicit backend selection
with BTreeRunner(backend="trio") as runner:
runner.run(my_tree)
with BTreeRunner(backend="asyncio+uvloop") as runner:
runner.run(my_tree)
runner.run(target, *args, **kwargs) signature is unchanged — callable in both 2.x and 3.0.0.
run()
Old signature: run(kernel, target, *args) — curio-only, deprecated.
New signature: run(target, *args, backend="asyncio", **kwargs).
# before — deprecated, asyncio path via asyncio.Runner directly
import asyncio
asyncio.run(my_tree())
# after — use bt.run()
from async_btree import run
run(my_tree)
run(my_tree, backend="asyncio+uvloop")
parallele — success_threshold typo fixed
The parameter was previously succes_threshold (one 's'). It is now success_threshold.
# before
parallele(children=[a, b], succes_threshold=1)
# after
parallele(children=[a, b], success_threshold=1)
sequence — success_threshold typo fixed
Same typo fix applies to sequence:
# before
sequence(children=[a, b, c], succes_threshold=2)
# after
sequence(children=[a, b, c], success_threshold=2)
The property shown in stringify_analyze output changes from succes_threshold: N to
success_threshold: N.
ControlFlowException.instanciate → instantiate
The method was renamed to fix a typo:
# before
ControlFlowException.instanciate(e)
# after
ControlFlowException.instantiate(e)
Behavior is unchanged — pure rename.
repeat_until semantics corrected — repeat_while added
The old repeat_until looped while condition was truthy, which is the opposite of
standard BT semantics. 3.0.0 corrects this:
| Function | Loops while condition is... |
|---|---|
repeat_while(condition, child) |
truthy (replaces old repeat_until behavior) |
repeat_until(condition, child) |
falsy (standard BT semantics — stops when condition fires) |
# before — looped while condition was truthy
repeat_until(condition=my_condition, child=my_child)
# after — same behavior, correct name
repeat_while(condition=my_condition, child=my_child)
# new repeat_until — loops until condition becomes truthy
repeat_until(condition=my_condition, child=my_child)
ContextVar isolation — behaviour unchanged
Asyncio users already had isolation via copy_context() in BTreeRunner.__enter__.
Behaviour is the same in 3.0.0: mutations inside a run do not escape to the caller.
The one difference: with the old asyncio.Runner persistent loop, successive runner.run()
calls shared ContextVar state. With anyio, each call is isolated from the base snapshot.
# before — mutations accumulated across ticks (asyncio.Runner persistent loop)
with BTreeRunner() as runner:
runner.run(set_var_to_42)
runner.run(read_var) # would see 42
# after — each tick starts from the __enter__ snapshot
with BTreeRunner() as runner:
runner.run(set_var_to_42)
runner.run(read_var) # sees original value, not 42
If your code relied on cross-tick ContextVar accumulation, pass state explicitly
via function arguments or use a mutable object (list, dict) set before __enter__.
See tutorial_3_context.py for details.
pytest migration
Replace pytest-asyncio with pytest-anyio (bundled with anyio):
# before
import pytest
pytestmark = pytest.mark.asyncio
async def test_something():
...
# after
import pytest
pytestmark = pytest.mark.anyio
async def test_something():
...
Remove asyncio_mode = "auto" from pyproject.toml if present — anyio does not use it.
Parametrize over backends (optional, recommended):
@pytest.fixture(params=["asyncio", "trio", "asyncio+uvloop"])
def anyio_backend(request):
backend = request.param
if backend == "asyncio+uvloop":
return "asyncio", {"use_uvloop": True}
return backend, {}