##// END OF EJS Templates
back to development
back to development

File last commit:

r24665:8e7c5f09
r24941:29011904
Show More
async_helpers.py
165 lines | 4.4 KiB | text/x-python | PythonLexer
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 """
Async helper function that are invalid syntax on Python 3.5 and below.
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 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.
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 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.
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 """
import ast
import sys
from textwrap import dedent, indent
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 class _AsyncIORunner:
def __call__(self, coro):
"""
Handler for asyncio autoawait
"""
import asyncio
return asyncio.get_event_loop().run_until_complete(coro)
Matthias Bussonnier
reformat with black
r24474
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 def __str__(self):
return 'asyncio'
_asyncio_runner = _AsyncIORunner()
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
def _curio_runner(coroutine):
"""
handler for curio autoawait
"""
import curio
Matthias Bussonnier
reformat with black
r24474
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 return curio.run(coroutine)
Matthias Bussonnier
Add pseudo sync mode
r24481 def _trio_runner(async_fn):
Matthias Bussonnier
att triio runner at top level now that 3.4 drop + docs
r24480 import trio
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490
Matthias Bussonnier
att triio runner at top level now that 3.4 drop + docs
r24480 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
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490
Matthias Bussonnier
Add pseudo sync mode
r24481 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.
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 raise RuntimeError(
"{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
)
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
def _asyncify(code: str) -> str:
"""wrap code in async def definition.
And setup a bit of context to run it later.
"""
Matthias Bussonnier
reformat with black
r24474 res = dedent(
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 """
Paul Ganssle
Fix indentation problem with _asyncify...
r24487 async def __wrapper__():
try:
{usercode}
finally:
locals()
"""
).format(usercode=indent(code, " " * 8))
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 return res
Paul Ganssle
Improve async detection mechanism with blacklist...
r24485 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)
"""
felixzhuologist
generalize to arbitrary errors by depth
r24665 def __init__(self):
self.depth = 0
felixzhuologist
check for nonlocal inside toplevel functions
r24664 super().__init__()
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490
Paul Ganssle
Improve async detection mechanism with blacklist...
r24485 def generic_visit(self, node):
func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
felixzhuologist
generalize to arbitrary errors by depth
r24665 invalid_types_by_depth = {
0: (ast.Return, ast.Yield, ast.YieldFrom),
1: (ast.Nonlocal,)
}
should_traverse = self.depth < max(invalid_types_by_depth.keys())
if isinstance(node, func_types) and should_traverse:
self.depth += 1
felixzhuologist
check for nonlocal inside toplevel functions
r24664 super().generic_visit(node)
felixzhuologist
generalize to arbitrary errors by depth
r24665 elif isinstance(node, invalid_types_by_depth[self.depth]):
Paul Ganssle
Improve async detection mechanism with blacklist...
r24485 raise SyntaxError()
else:
super().generic_visit(node)
felixzhuologist
generalize to arbitrary errors by depth
r24665
Paul Ganssle
Improve async detection mechanism with blacklist...
r24485 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)
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 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:
Matthias Bussonnier
fix runnign on 3.7
r24468 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
# 3.7+, but just does not _compile_
Matthias Bussonnier
reformat with black
r24474 compile(cell, "<>", "exec")
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 return False
except SyntaxError:
try:
Paul Ganssle
Improve async detection mechanism with blacklist...
r24485 parse_tree = _async_parse_cell(cell)
# Raise a SyntaxError if there are top-level return or yields
v = _AsyncSyntaxErrorVisitor()
v.visit(parse_tree)
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 except SyntaxError:
return False
return True
return False