[Async-sig] Optional async method and best practices

Nathaniel Smith njs at pobox.com
Wed Jul 12 19:44:10 EDT 2017


On Tue, Jul 11, 2017 at 3:26 PM, Laurent Mazuel via Async-sig
<async-sig at python.org> wrote:
> 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 advice would be to

- think how you'd write the API if it were synchronous, and then do
exactly that, except marking the blocking functions/methods as async

- pretend that the only allowed syntax for using await or coroutines
is 'await fn(...)', and treat the 'await obj' syntax as something that
only exists for compatibility with legacy code that uses explicit
Future/Deferred objects.

One of Python's famous design principles is "there should be one (and
preferably only one) obvious way to do it". The word "obvious" there
is important -- because programming is so flexible, and Python in
particular is so flexible, there generally are tons and tons of ways
to do anything in Python. Like, if you want to make an http request,
that could be a function call, `requests.get(url)`. Or you could have
a class that does the request when you access some property,
`Request(url).doit`. Or you could have a class whose `__str__` method
does the request, and you monkeypatch a StringIO in place of
sys.stdout and use print, like `sys.stdout = io.StringIO();
print(Request(url), end=""); body = sys.stdout.getvalue()`. Why not,
the computer doesn't care! These are all equally compliant with the
Python language specification.

Fortunately, we have some strong conventions about these things, and
most of these options would never even occur to most people. *Of
course* the obvious way to make an HTTP request is to call a function.
The other options are ridiculous. And that makes life much easier,
because we don't need to stop every time we implement some trivial
function and be like "hmm, what if I did this using monkeypatching and
two metaclasses? Would that be a good idea?", and it means that when
you use a new library you (mostly) don't have to worry that they're
secretly using monkeypatching and metaclasses to implement some
trivial functionality, etc. Yay conventions.

But *un*fortunately, async in Python is so new and shiny that we
currently have all this flexibility, but we don't have conventions
yet, so people try all kinds of wacky stuff and no-one's sure what to
recommend. There's lots of ways to do it, but no-one knows which one
is obvious.

The nice thing about my rules above is that they give you one obvious
way to do it, and minimize the delta between sync and async code. You
already know how functions and synchronous APIs work, your users
already know how functions and synchronous APIs work, all you need to
add is some 'async' and 'await' keywords and you're good to go.

I think forcing users to know what "coroutine objects" are before they
can write async I/O code – or even deal with the word "coroutine" at
all – is like forcing users to understand the nuances of metaclass
property lookup and the historical mess around nb_add/sq_concat before
they can use a+b to add two numbers. Obviously in both cases it's
important that the full details are available for those who want to
dig into it, but you shouldn't need that just to make some HTTP
requests.

tl;dr: Treat async functions as a kind of function with a funny
calling convention.

-n

-- 
Nathaniel J. Smith -- https://vorpus.org


More information about the Async-sig mailing list