Skip to content

Multi-line input doesn't work in asyncio old REPL #140013

@bswck

Description

@bswck

Bug report

Bug description:

During helping triaging #140002, I've learned that asyncio incorrectly calls code.InteractiveColoredConsole.runsource, which is reserved for the new REPL only, because the input is expected to be complete. This results in the asyncio REPL mishandling multi-line input:

❯ PYTHON_BASIC_REPL=1 ./python -m asyncio
asyncio REPL 3.15.0a0 (heads/main:5f91d5d9a41, Oct 12 2025, 22:21:59) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> async def foo():
  File "<stdin>", line 1
    async def foo():
                    ^
IndentationError: expected an indented block after function definition on line 1

We may as well make stdin non-interactive (which forces the basic REPL, the new REPL requires a TTY) and reproduce with a bash one-liner:

❯ ./python -m asyncio <<< $'async def foo():\n    print("Hello!")\n\nawait foo()'
asyncio REPL 3.15.0a0 (heads/main:5f91d5d9a41, Oct 12 2025, 22:21:59) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>>   File "<stdin>", line 1
    async def foo():
                    ^
IndentationError: expected an indented block after function definition on line 1
>>>   File "<stdin>", line 1
    print("Hello!")
IndentationError: unexpected indent
>>> >>> Traceback (most recent call last):
  File "/home/bswck/Python/cpython/Lib/concurrent/futures/_base.py", line 450, in result
    return self.__get_result()
           ~~~~~~~~~~~~~~~~~^^
  File "/home/bswck/Python/cpython/Lib/concurrent/futures/_base.py", line 395, in __get_result
    raise self._exception
  File "<stdin>", line 1, in <module>
    await foo()
          ^^^
NameError: name 'foo' is not defined
>>> 
exiting asyncio REPL...

and monkey-patch the runsource implementation one MRO entry higher to mitigate the exception:

❯ ./python -m asyncio <<< $'import __main__, code; __main__.console.runsource = code.InteractiveConsole.runsource.__get__(__main__.console)\nasync def foo(): print("Hello!")\n\nawait foo()'
asyncio REPL 3.15.0a0 (heads/main:5f91d5d9a41, Oct 12 2025, 22:21:59) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> >>> ... >>> Hello!
>>> 
exiting asyncio REPL...

I'm seeing one easy workaround here that builds on top of the existing asyncio REPL's compatibility code -- override code.InteractiveColoredConsole.runsource to simply run code.InteractiveConsole.runsource() on self when CAN_USE_PYREPL is false. The entire flow of these abstractions is very weirdly tangled already, but I think that this change is surgical enough to not be a huge problem.

We could also inherit from code.InteractiveConsole instead of code.InteractiveColoredConsole, if CAN_USE_PYREPL is false, either by moving where the class is defined or reassigning __bases__, but that complicates a lot and without duplicating code loses us colored tracebacks that are the original behavior of sys._baserepl. We likely don't want to do that.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions