Skip to content

Concurrency and async

Every Client method is synchronous and blocking: it issues the request, waits for the node to respond, and returns a plain Python value. There are no coroutines. This page covers using the client from threads and from asyncio applications.

The common case. Create one connection and one client and call methods in sequence:

from calimero_client_py import create_connection, create_client
client = create_client(create_connection(api_url="http://localhost:2428"))
for ctx in client.list_contexts():
print(ctx)

A Client is backed by an internal HTTP client and runtime and can be shared across threads — calls release the GIL while waiting on the node, so several threads making requests will overlap their I/O:

from concurrent.futures import ThreadPoolExecutor
client = create_client(create_connection(api_url="http://localhost:2428"))
def sync_one(context_id):
return client.sync_context(context_id)
with ThreadPoolExecutor(max_workers=4) as pool:
results = list(pool.map(sync_one, context_ids))

Because the calls block, calling them directly inside a coroutine would stall the event loop for the duration of the request. Offload each call to a worker thread with asyncio.to_thread (Python 3.9+):

import asyncio
from calimero_client_py import create_connection, create_client
client = create_client(create_connection(api_url="http://localhost:2428"))
async def main():
# runs the blocking call on a thread; the event loop stays responsive
contexts = await asyncio.to_thread(client.list_contexts)
print(f"found {len(contexts)} contexts")
# fan several calls out concurrently
results = await asyncio.gather(
*(asyncio.to_thread(client.sync_context, cid) for cid in context_ids)
)
asyncio.run(main())

This keeps the “no await on client methods” rule intact — you await the to_thread wrapper, not the client call itself.