Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6f33fcd
Prototype async REPL using IPython, take III
Carreau Mar 10, 2017
9f21667
fix UnboundLocalError
Carreau Aug 14, 2018
c5b1e05
titleto have doc build
Carreau Aug 14, 2018
05789e8
fix docs
Carreau Aug 14, 2018
4fac0cb
Load the asycn ext only on 3.5+
Carreau Aug 14, 2018
5ea200e
fix runnign on 3.7
Carreau Aug 14, 2018
b6c88e4
remove duplicate WatsNew from bad rebase
Carreau Aug 16, 2018
702b946
test with trio
Carreau Aug 16, 2018
c447030
3.7 still nto on travis
Carreau Aug 16, 2018
3c0b3aa
reformat with black
Carreau Aug 16, 2018
390bb17
doc, remove appveyor 3.4
Carreau Aug 16, 2018
3797f31
move infor to the right place
Carreau Aug 16, 2018
da3e46e
DeprecationWarning
Carreau Aug 16, 2018
521e8e5
runblack on new tests
Carreau Aug 16, 2018
9f6be15
att triio runner at top level now that 3.4 drop + docs
Carreau Aug 19, 2018
2b7f067
Add pseudo sync mode
Carreau Aug 19, 2018
58c1419
no f-strings
Carreau Aug 19, 2018
8d38b3c
Only trigger async loop runner when needed. Allows running files that
dalejung Aug 21, 2018
c4bf632
Improve async detection mechanism with blacklist
pganssle Aug 26, 2018
b210342
Add tests for SyntaxError with top-level return
pganssle Aug 26, 2018
422664a
Fix indentation problem with _asyncify
pganssle Aug 27, 2018
d0b1496
Fix async_helpers tests
pganssle Aug 27, 2018
342427e
Refactor and tweak async-in-function tests
pganssle Aug 27, 2018
effc242
docs cleanup, reformat code, remove dead code.
Carreau Aug 27, 2018
7cc4940
remove trio from test requirement and skipp if not there
Carreau Aug 27, 2018
f384e25
fix test
Carreau Aug 27, 2018
a1e70fc
Test that using wrong runner/coroutine pair does not crash.
Carreau Aug 28, 2018
c11291b
run tests with trio and curio on travis
Carreau Aug 28, 2018
2702d84
add notes some magic don't work yet
Carreau Aug 31, 2018
9983091
typo
Carreau Sep 4, 2018
85db703
Tool to go around sphinx limitation during developpement
Carreau Sep 6, 2018
e4c4b83
run doc fixing tool on travis
Carreau Sep 6, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ python:
- "3.7-dev"
- 3.6
- 3.5
- 3.4
sudo: false
env:
global:
Expand All @@ -17,6 +16,7 @@ install:
- pip install pip --upgrade
- pip install setuptools --upgrade
- pip install -e file://$PWD#egg=ipython[test] --upgrade
- pip install trio curio
- pip install codecov check-manifest --upgrade
- sudo apt-get install graphviz
script:
Expand All @@ -26,6 +26,7 @@ script:
- |
if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then
pip install -r docs/requirements.txt
python tools/fixup_whats_new_pr.py
make -C docs/ html SPHINXOPTS="-W"
fi
after_success:
Expand Down
6 changes: 3 additions & 3 deletions IPython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
#-----------------------------------------------------------------------------

# Don't forget to also update setup.py when this changes!
if sys.version_info < (3,4):
if sys.version_info < (3, 5):
raise ImportError(
"""
IPython 7.0+ supports Python 3.4 and above.
IPython 7.0+ supports Python 3.5 and above.
When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
Python 3.3 was supported up to IPython 6.x.
Python 3.3 and 3.4 were supported up to IPython 6.x.

See IPython `README.rst` file for more information:

Expand Down
157 changes: 157 additions & 0 deletions IPython/core/async_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""
Async helper function that are invalid syntax on Python 3.5 and below.

This code is best effort, and may have edge cases not behaving as expected. In
particular it contain a number of heuristics to detect whether code is
effectively async and need to run in an event loop or not.

Some constructs (like top-level `return`, or `yield`) are taken care of
explicitly to actually raise a SyntaxError and stay as close as possible to
Python semantics.
"""


import ast
import sys
from textwrap import dedent, indent


class _AsyncIORunner:

def __call__(self, coro):
"""
Handler for asyncio autoawait
"""
import asyncio

return asyncio.get_event_loop().run_until_complete(coro)

def __str__(self):
return 'asyncio'

_asyncio_runner = _AsyncIORunner()


def _curio_runner(coroutine):
"""
handler for curio autoawait
"""
import curio

return curio.run(coroutine)


def _trio_runner(async_fn):
import trio

async def loc(coro):
"""
We need the dummy no-op async def to protect from
trio's internal. See https://github.com/python-trio/trio/issues/89
"""
return await coro

return trio.run(loc, async_fn)


def _pseudo_sync_runner(coro):
"""
A runner that does not really allow async execution, and just advance the coroutine.

See discussion in https://github.com/python-trio/trio/issues/608,

Credit to Nathaniel Smith

"""
try:
coro.send(None)
except StopIteration as exc:
return exc.value
else:
# TODO: do not raise but return an execution result with the right info.
raise RuntimeError(
"{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
)


def _asyncify(code: str) -> str:
"""wrap code in async def definition.

And setup a bit of context to run it later.
"""
res = dedent(
"""
async def __wrapper__():
try:
{usercode}
finally:
locals()
"""
).format(usercode=indent(code, " " * 8))
return res


class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
"""
Find syntax errors that would be an error in an async repl, but because
the implementation involves wrapping the repl in an async function, it
is erroneously allowed (e.g. yield or return at the top level)
"""

def generic_visit(self, node):
func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
invalid_types = (ast.Return, ast.Yield, ast.YieldFrom)

if isinstance(node, func_types):
return # Don't recurse into functions
elif isinstance(node, invalid_types):
raise SyntaxError()
else:
super().generic_visit(node)


def _async_parse_cell(cell: str) -> ast.AST:
"""
This is a compatibility shim for pre-3.7 when async outside of a function
is a syntax error at the parse stage.

It will return an abstract syntax tree parsed as if async and await outside
of a function were not a syntax error.
"""
if sys.version_info < (3, 7):
# Prior to 3.7 you need to asyncify before parse
wrapped_parse_tree = ast.parse(_asyncify(cell))
return wrapped_parse_tree.body[0].body[0]
else:
return ast.parse(cell)


def _should_be_async(cell: str) -> bool:
"""Detect if a block of code need to be wrapped in an `async def`

Attempt to parse the block of code, it it compile we're fine.
Otherwise we wrap if and try to compile.

If it works, assume it should be async. Otherwise Return False.

Not handled yet: If the block of code has a return statement as the top
level, it will be seen as async. This is a know limitation.
"""

try:
# we can't limit ourself to ast.parse, as it __accepts__ to parse on
# 3.7+, but just does not _compile_
compile(cell, "<>", "exec")
return False
except SyntaxError:
try:
parse_tree = _async_parse_cell(cell)

# Raise a SyntaxError if there are top-level return or yields
v = _AsyncSyntaxErrorVisitor()
v.visit(parse_tree)

except SyntaxError:
return False
return True
return False
Loading