ipython / ipython

Official repository for IPython itself. Other repos in the IPython organization contain things like the website, documentation builds, etc.
https://ipython.readthedocs.org
BSD 3-Clause "New" or "Revised" License
16.24k stars 4.43k forks source link

Docstrings are indented when using await #11664

Open twavv opened 5 years ago

twavv commented 5 years ago
code = """
import numpy as np
import matplotlib.pyplot as plt
plt.imshow(np.random.randn(10, 10))
plt.show()
"""

print(code)
await asyncio.sleep(1)

yields

        import numpy as np
        import matplotlib.pyplot as plt
        plt.imshow(np.random.randn(10, 10))
        plt.show()

with indentation (8 indents to be exact), whereas dropping the await doesn't.

I figured this belonged here rather than in Jupyter notebook, but I'm using the notebook to run this example (I'm not sure if it would be reproducible using ipython from the CLI since I imagine the code = ... and await ... get run in different execution requests).

For now, I can just textwrap.dedent, but thought I'd file the issue nonetheless.

twavv commented 5 years ago

The offending code is this function. It is called whenever there is an await statement at the top level. https://github.com/ipython/ipython/blob/f0f6cd8b8c9f74ea8b2c5e37b6132212ce661c28/IPython/core/async_helpers.py#L77-L91

It wraps a block in an async function and naively indents it by eight. I'm not super familiar with AST parsing (especially using the builtin ast module), but there isn't (or at the very least I don't see) a way to check if a given string is a triple-quote string via the ast module.

It'd be a shame if this were just silently broken. At the very least, we could warn if we detect the use of triple-quotes together with await (even a simple heuristic along the lines of if '"""' in code: ..., as well as only warning once to avoid spamming the user).

twavv commented 5 years ago

(tag @Carreau since they seem to be the one most involved with asyncio in the REPL)

Carreau commented 5 years ago

Thanks for the ping.

I'm hopping to get some of this natively handled by CPython at some point. I'll see if I can do something when I have some time.

twavv commented 5 years ago

Great! If there's anything I (or anyone else) can do, feel free to suggest next steps.

Carreau commented 5 years ago

Well if you figure out even a prototype of how we can do this... that would help. Or even just how to access or detect the strings that would need fixing. I'm going to assume we want to wrap those in some kind of texwrap.dedent() that only affect multiple lines strings? And do not touch the first line?

Jackenmen commented 4 years ago

I'm not sure if it would be reproducible using ipython from the CLI

Since I haven't seen anyone confirming that it's reproducible on IPython from the cli, I'm confirming :P It works fine for Python 3.8+ because different logic is used then, but it doesn't work for pre-3.8 (tested on Python 3.7):

In [1]: import asyncio
   ...: text = '''
   ...: text
   ...: '''
   ...: print(repr(text))
   ...: await asyncio.sleep(0)
'\n        text\n        '

In [2]: import asyncio
   ...: text = '''
   ...: text
   ...: '''
   ...: print(repr(text))
'\ntext\n'
Carreau commented 4 years ago

Yeah, Ithink it's reasonable to tell users that using 3.8+ for this to work is reasonable. 3.7 is best effort.