I created a solution that does the work that is close to necessary. The idea is to use a wrapper for Browser() , which uses __getattr__ and __call__ to collect an action (like getting an attribute or call) and return self to catch the next one action. After all the collected actions, we will βcatchβ yiled from wrapper with __iter__ and process all the collected actions.
import asyncio def chain(obj): """ Enables coroutines chain for obj. Usage: text = yield from chain(obj).go().click().attr Note: Returns not coroutine, but object that can be yield from. """ class Chain: _obj = obj _queue = []
Using:
class Browser: @asyncio.coroutine def go(self): print('go') return self @asyncio.coroutine def click(self): print('click') return self def text(self): print('text') return 5 @asyncio.coroutine def main(): text = yield from chain(Browser()).go().click().go().text() print(text) loop = asyncio.get_event_loop() loop.run_until_complete(main())
Conclusion:
go click go text 5
Note that chain() does not return a real coroutine, but an object that can be used as a coroutine on yield from . We need to wrap the result of chain() to get a normal coroutine that can be passed to any asyncio function that requires a coroutine:
@asyncio.coroutine def chain_to_coro(chain): return (yield from chain) @asyncio.coroutine def main(): ch = chain(Browser()).go().click().go().text() coro = chain_to_coro(ch) results = yield from asyncio.gather(*[coro], return_exceptions=True) print(results)
Conclusion:
go click go text [5]
source share