Skip to content

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

Closed as not planned
Closed as not planned

Description

@johnslavik

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

Activity

  1. johnslavik commented on Oct 12, 2025

    @johnslavik
    MemberAuthor

    One tricky thing about this is that there is still a small incompatibility between the new and the old REPL in terms of compilation.
    Because the new REPL splits the input into statements and compiles it with mode="exec" and then wraps particular statements with ast.Interactive / ast.Module and re-compiles (finalizes from AST-only form) with mode="exec" / mode="single", the new REPL would accept multiple interactive statements in one prompt while the old REPL wouldn't, leading to this effect:

    ❯ 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():
    ...     pass
    ... await foo()
      File "<stdin>", line 3
        await foo()
        ^^^^^
    SyntaxError: invalid syntax
    >>> async def foo():
    ...     pass
    ... 
    >>> await foo()
    >>> 
    exiting asyncio REPL...

    FWIW, the regular old Python REPL struggles with the same problem:

    ❯ PYTHON_BASIC_REPL=1 ./python
    Python 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
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def foo():  
    ...     pass
    ... foo()
      File "<stdin>", line 3
        foo()
        ^^^
    SyntaxError: invalid syntax
    >>> 

    So this isn't a flaw of this solution, just a flaw of the old REPL.
    We can definitely improve the error message though, I think.

  2. kumaraditya303 commented on Oct 23, 2025

    @kumaraditya303
    Contributor

    Since this has never worked, instead of adding this why not just recommend to use new repl instead?

  3. johnslavik commented on Oct 23, 2025

    @johnslavik
    MemberAuthor

    Since this has never worked, instead of adding this why not just recommend to use new repl instead?

    How would this recommendation of the new REPL be implemented?

  4. kumaraditya303 commented on Oct 23, 2025

    @kumaraditya303
    Contributor

    How would this recommendation of the new REPL be implemented?

    We can recommend to not use PYTHON_BASIC_REPL with asyncio repl at https://docs.python.org/3.13/library/asyncio.html

  5. johnslavik commented on Oct 23, 2025

    @johnslavik
    MemberAuthor

    It is unclear exactly how many people would benefit from my original patch, so I agree on deferring it.

    The basic REPL also runs as a fallback if the new REPL can't be launched, so I think it has to be more general.

    How about "It is recommended to use the asyncio REPL in the Python-based REPL mode"?

  6. kumaraditya303 commented on Oct 24, 2025

    @kumaraditya303
    Contributor

    How about "It is recommended to use the asyncio REPL in the Python-based REPL mode"?

    Yes, I think that's appropriate, we can iterate over the text in the PR.

  7. johnslavik commented on Oct 24, 2025

    @johnslavik
    MemberAuthor

    How about "It is recommended to use the asyncio REPL in the Python-based REPL mode"?

    Yes, I think that's appropriate, we can iterate over the text in the PR.

    Filed #140569 to track. Closing this as not planned.

  8. moved this from Todo to Done in asyncioon Oct 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions