[Async-sig] Optional async method and best practices
Laurent Mazuel
lmazuel at microsoft.com
Tue Jul 11 18:26:58 EDT 2017
Hello,
I’m working currently with Brett Cannon to bring asyncio support to our SDK. We wanted to check with you one of the scenario, since we got a loooong discussion on it together 😊. And we want to do it using the best reasonable practice with your opinion.
We have an api that is clearly async and will gain a lot to be converted to asyncio. However, it's a two-step operation. Operation 1 asks for the creation of a resource and is not async, operation 2 is *optional* and wait for completion of this creation (with nightmare threads currently and I removed a lot of code moving to asyncio - happiness). There is perfectly legit scenarios where operation 2 is not needed and avoid it is better, but it has to be prepared at the same time of operation 1. Current code looks like this:
sync_poller = client.create(**parameters)
obj = sync_poller.resource() # Get the initial resource information, but the object is not actually created yet.
obj = sync_poller.result() # OPTIONAL. This is a blocking call with thread, if you want to wait for actual creation and get updated metadatas
My first prototype was to split and return a tuple (resource, coroutine):
obj, optional_poller = client.create(**parameters)
obj = await optional_poller # OPTIONAL
But I got a warning if I decide to do not use this poller, RuntimeWarning: coroutine 'foo' was never awaited
I was surprised honestly that I can't do that, since I feel like I'm not leaking anything. I didn't run the operation, so there is no wasted resource at my knowledge. But I remember wasting time because of a forgotten "yield from", so I guess it's fair 😊. But I would be curious to understand what I did badly.
I found 2 solutions to avoid the warning, and I currently prefer solution 2:
1- Return a function to call, and not a coroutine. The "await" statement becomes:
obj = await optional_poller()
2- Return my initial object with an async method. This allows me to write (something finally close to the current code):
async_poller = client.create(**parameters)
obj = async_poller.resource() # Get the initial resource information, but the object is not actually created yet.
obj = await async_poller.result() # OPTIONAL
My async_poller object being something like:
class PollerOperation:
async def result(self):
...async version of previous sync result()...
So the questions are:
- Does this seem a correct pattern?
- Is there a simple way to achieve something like this:
obj = await async_poller
meaning, I can win the "result()" syntax and directly "await" on the object and get the result from magic function. I tried by subclassing some ABC coroutine/awaitable, but wasn't able to find a correct syntax. I'm not even sure this makes sense and respects the zen of Python 😊
If it helps, I'm willing to use 3.5 as minimal requirement to get async behavior.
Thank you!!
Laurent
More information about the Async-sig
mailing list