Welcome to asyncode’s documentation!

The goal of this module is to allow to emulate Python’s interactive interpreter in already running asnychronous contexts.

For example, it allows to debug a Discord bot (built with Discord.py, that uses async mechanisms) directly inside Discord client, by processing messages sent in a channel as interpreter commands.

This module source code is based on code standard module by Guido van Rossum and co-othors, and on asyncio.__main__ code by Yury Selivanov.

It differs from the latter in that its classes operate in already running asynchronous contexts, while running python -m asyncio create and run a new asyncio loop.

Rationale

Several issues prevent directly using Python’s code standard module :

  • code.InteractiveConsole and code.InteractiveInterpreter methods are not asynchronous, so the interpreter will be blocking the loop between two inputs: one solution would be to run it in a specific thread, but this adds a lot of complexity and potential error sources;

  • the code compiler (codeop.CommandCompiler instance) disallow using await statements outside of functions, so it would be very unpleasant to await coroutines inside the interpreter (declare a asynchronous function and use asyncio.get_running_loop());

  • information printed by print(), repr()… (like when sending an object name) to sys.stdout and sys.stderr would be lost, as we need an asynchronous function to send content to the interpreter.

Implementation

This module provides two classes, AsyncInteractiveInterpreter and AsyncInteractiveConsole, who do the exact same job as standard code.InteractiveInterpreter and code.InteractiveConsole, except that:

  • write(), raw_input() and other higher-level methods are asynchronous (they return coroutines, that have to be awaited).

    Warning

    Only text input/output is asnychronous: in particular, code executed in the interpreter will still be blocking.

    For example, sending time.sleep(10) in a running AsyncInteractiveConsole will block every other concurrent task for 10 seconds.

  • the compiler allows top-level await statements;

  • instead of being directly executed, compiled code is wrapped in a function object (types.FunctionType) which is called: if the result is a coroutine, it is then awaited; otherwise, this is strictly identical to direct code execution.

  • sys.stdout and sys.stderr are catch during code execution, then gathered content is written in the interpreter. This behavior can be turned off with reroute_stdout and reroute_stderr arguments.

Usage

asyncode is meant to be used in existing asynchronous environments. Minimal working code will need to subclass provided classes to implement appropriate functions:

import asyncode

class MyAsyncConsole(asyncode.AsyncInteractiveConsole):
    """AsyncInteractiveConsole adapted to running environment"""

    async def write(self, data):
        """Use appropriate method"""
        await some_output_coroutine(data)

    async def raw_input(self, prompt=""):
        """Use appropriate method"""
        if prompt:
            await some_output_coroutine(prompt)

        data = await some_input_coroutine()
        return data


async def run_interpreter():
    """Run an interactive Python interpreter"""
    console = MyAsyncConsole()
    try:
        await console.interact()
    except SystemExit:
        # Do not exit the whole program!
        await some_output_coroutine("Bye!")

Warning

As for code classes, SystemExit exceptions are not catched by provided methods. This mean that quit()/exit() commands run inside the console will quit your whole program if not explicitly catched (as in this example), which may not be what you want.

Note

High-level async environments may not provide a way to send EOF or KeyboardInterrupt signals to the console. Custom implementations should not forget a exit mechanism (like recognize some custom message), or just catch SystemExit (as in this example).

API Reference

Emulating Python’s interactive interpreter in asynchronous contexts.

class asyncode.AsyncInteractiveInterpreter(locals, *, reroute_stdout=True, reroute_stderr=True)[source]

Base class for AsyncInteractiveConsole.

This class inherits from code.InteractiveInterpreter. It
  • makes runsource, runcode, showsyntaxerror, showtraceback and write methods asynchronous (they return coroutines, that have to be awaited);

  • tell the compiler to allow top-level await statements;

  • awaits coroutines returned by such statements execution;

  • catches sys.stdout and sys.stderr during code execution, then send gathered content using write().

This class deals with parsing and interpreter state (the user’s namespace); it doesn’t deal with input buffering or prompting or input file naming (the filename is always passed in explicitly).

__init__(locals, *, reroute_stdout=True, reroute_stderr=True)[source]

Constructor.

The optional locals argument specifies the dictionary in which code will be executed; it defaults to a newly created dictionary with key "__name__" set to "__console__" and key "__doc__" set to None.

The optional reroute_stdout and reroute_stderr keyword arguments, if set to False, disable catching sys.stdout and sys.stderr, respectively.

async runsource(source, filename='<input>', symbol='single')[source]

Compile and run some source in the interpreter.

Arguments and behavior are as for code.InteractiveInterpreter, except that this is an asynchronous method.

Unless explicitly disabled, this method catches sys.stdout and sys.stderr during code execution. If some data has been written, it is send using write() before returning.

async runcode(code)[source]

Execute a code object.

Arguments and behavior are as for code.InteractiveInterpreter, except that this is an asynchronous method and that ‘code’ is wrapped in a function object which is called instead of being directly executed.

If the result is a coroutine, it is then awaited before this method returns. Exceptions are processed in the same way as during code execution.

async showsyntaxerror(filename=None)[source]

Display the syntax error that just occurred.

Arguments and behavior are as for code.InteractiveInterpreter, except that this is an asynchronous method.

async showtraceback()[source]

Display the exception that just occurred.

Arguments and behavior are as for code.InteractiveInterpreter, except that this is an asynchronous method.

async write(data)[source]

Write a string.

Arguments are as for code.InteractiveInterpreter. While that this is an async method, the base implementation still writes to sys.stderr; a subclass should replace this with a different (asynchronous) implementation.

class asyncode.AsyncInteractiveConsole(locals=None, filename='<console>', *, reroute_stdout=True, reroute_stderr=True)[source]

Emulate interactive Python interpreter in asynchronous contexts.

This class builds on AsyncInteractiveInterpreter and adds prompting using the familiar sys.ps1 and sys.ps2, and input buffering.

It does exactly the same job as code.InteractiveConsole, except that interact, push and raw_input methods are asynchronous (they return coroutines, that have to be awaited).

__init__(locals=None, filename='<console>', *, reroute_stdout=True, reroute_stderr=True)[source]

Constructor.

The optional locals, reroute_stdout and reroute_stderr arguments will be passed to the AsyncInteractiveInterpreter base class. The optional filename argument should specify the (file)name of the input stream; it will show up in tracebacks.

async interact(banner=None, exitmsg=None)[source]

Closely emulate the interactive Python console.

Arguments and behavior are as for code.InteractiveConsole, except that this is an asynchronous method that awaits inputs.

async push(line)[source]

Push a line to the interpreter.

Arguments and behavior are as for code.InteractiveConsole, except that this is an asynchronous method.

async raw_input(prompt='')[source]

Write a prompt and read a line.

Arguments and behavior are as for code.InteractiveConsole. While that this is an async method, the base implementation still uses the built-in function input(); a subclass should replace this with a different (asynchronous) implementation.

Indices and tables