from __future__ import unicode_literals import types from prompt_toolkit.eventloop.defaults import get_event_loop from prompt_toolkit.eventloop.future import Future __all__ = [ 'From', 'Return', 'ensure_future', ] def ensure_future(future_or_coroutine): """ Take a coroutine (generator) or a `Future` object, and make sure to return a `Future`. """ if isinstance(future_or_coroutine, Future): return future_or_coroutine elif isinstance(future_or_coroutine, types.GeneratorType): return _run_coroutine(future_or_coroutine) else: raise ValueError('Expecting coroutine or Future object. Got %r: %r' % ( type(future_or_coroutine), future_or_coroutine)) class Return(Exception): """ For backwards-compatibility with Python2: when "return" is not supported in a generator/coroutine. (Like Trollius.) Instead of ``return value``, in a coroutine do: ``raise Return(value)``. """ def __init__(self, value): self.value = value def __repr__(self): return 'Return(%r)' % (self.value, ) def From(obj): """ Used to emulate 'yield from'. (Like Trollius does.) """ return ensure_future(obj) def _run_coroutine(coroutine): """ Takes a generator that can yield Future instances. Example: def gen(): yield From(...) print('...') yield From(...) ensure_future(gen()) The values which are yielded by the given coroutine are supposed to be `Future` objects. """ assert isinstance(coroutine, types.GeneratorType) loop = get_event_loop() result_f = loop.create_future() # Wrap this future in a `_FutureRef`. We need this in order to be able to # break all its references when we're done. This is important # because in case of an exception, we want to be sure that # `result_f.__del__` is triggered as soon as possible, so that we see the # exception. # (If `step_next` had a direct reference to `result_f` and there is a # future that references `step_next`, then sometimes it won't be cleaned up # immediately. - I'm not sure how exactly, but in that case it requires the # garbage collector, because refcounting isn't sufficient.) ref = _FutureRef(result_f) # Loop through the generator. def step_next(f=None): " Execute next step of the coroutine." try: if f is None: new_f = coroutine.send(None) else: exc = f.exception() if exc: new_f = coroutine.throw(exc) else: new_f = coroutine.send(f.result()) except StopIteration: # Stop coroutine. Make sure that a result has been set in the future, # this will call the callbacks. (Also, don't take any result from # StopIteration, it has already been set using `raise Return()`. if not ref.future.done(): ref.future.set_result(None) ref.forget() except Return as e: ref.future.set_result(e.value) ref.forget() except BaseException as e: ref.future.set_exception(e) ref.forget() else: # Process yielded value from coroutine. assert isinstance(new_f, Future), 'got %r' % (new_f, ) @new_f.add_done_callback def continue_(_): step_next(new_f) # Start processing coroutine. step_next() return result_f class _FutureRef(object): def __init__(self, future): self.future = future def forget(self): " Forget reference. " self.future = None