Moler separates the command/observer API from the execution backend. The same command objects can be driven by a threaded runner or by an asyncio runner — you choose via theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/nokia/moler/llms.txt
Use this file to discover all available pages before exploring further.
runner parameter or a configuration setting. This lets you integrate Moler into existing asyncio applications without rewriting commands.
Runners overview
| Runner variant | Class | Best for |
|---|---|---|
threaded | ThreadedRunner | Standard synchronous test scripts; simplest to use |
asyncio | AsyncioRunner | async def coroutine-based code; all work on one event loop |
asyncio-in-thread | AsyncioInThreadRunner | Synchronous (def) code that needs asyncio under the hood; mixes threads and asyncio |
- Your test harness already uses an asyncio event loop.
- You want to
awaitobservers directly withasyncio.wait_for(). - You want to avoid thread overhead for high-concurrency scenarios.
Using the asyncio runner
To use the asyncio runner, passrunner=get_runner(variant="asyncio") when creating an observer, or configure it in the YAML file.
- async def (asyncio runner)
- def (asyncio-in-thread runner)
Inside The
async def functions, use asyncio.wait_for() to await observers:await asyncio.wait_for(net_down_detector, timeout=10) syntax works because Moler command and observer objects implement __await__ via the runner’s wait_for_iterator().AsyncioRunner internals
TheAsyncioRunner (defined in moler/asyncio_runner.py) works as follows:
submit()
When an observer is started, the runner calls
submit(). This:- Gets or creates an asyncio event loop for the current thread.
- Calls
_start_feeding()— subscribes asecure_data_receivedcallback to the Moler connection so the observer starts receiving data immediately. - Schedules a
feed()coroutine viaasyncio.ensure_future(). - Returns the asyncio
Task(aFuture) for use bywait_for().
feed() coroutine
The
feed() coroutine runs inside the event loop. It polls every 5ms (await asyncio.sleep(0.005)) checking whether the observer is done or has timed out. When done, it unsubscribes the data receiver.wait_for()
wait_for() runs the event loop (via event_loop.run_forever()) until the observer’s future completes or a timeout occurs. If called from inside an already-running event loop (i.e., from async def code), it raises WrongUsage — use await observer or await asyncio.wait_for(observer, timeout) instead.AsyncioInThreadRunner
AsyncioInThreadRunner is designed for synchronous code. It uses a shared AsyncioLoopThread — a single background thread running an asyncio event loop that is created once on first use:
submit() blocks for up to 0.5 seconds waiting for the feeder coroutine to actually start — guaranteeing that no data is lost before the observer is ready.
Asyncio IO connections
Moler provides asyncio-native IO implementations inmoler/io/asyncio/:
AsyncioTerminal (in moler/io/asyncio/terminal.py) uses asyncio.SubprocessProtocol and a pty (pseudo-terminal) to spawn and interact with a shell subprocess inside the event loop, without threads.
Configuring runner variant in YAML
You can specify the connection variant in the YAML config so all devices and connections use asyncio without changing Python code:get_connection(io_type='terminal') returns an asyncio-backed terminal connection.
When to use which runner
The runner choice affects how observers are scheduled, not what they parse. All command classes work with all runners.
| Situation | Recommended runner |
|---|---|
| Simple sequential test scripts | threaded (default) |
| Test framework already uses asyncio event loop | asyncio |
| Synchronous code, high connection count, want asyncio efficiency | asyncio-in-thread |
Inside async def and want direct await syntax | asyncio |
Thread safety in the asyncio runner
TheAsyncioRunner protects against concurrent write access to observers using an observer_lock (a threading.Lock). This is important because data can arrive from a connection (potentially in a different thread or callback) while the runner’s wait_for() is also accessing the observer: