3 ms·
That's an clever way to go about solving the sync/async situation. I work in an ecosystem that has both Python 2 and Python 3.6 async applications, so my code
by spamizbad 8y ago
That's an clever way to go about solving the sync/async situation.
I work in an ecosystem that has both Python 2 and Python 3.6 async applications, so my code has to play nice with both. I often end up writing APIs as such:
from __future__ import [Python 3 niceties]
class BaseApi(object):
def _method_that_does_io(self, *args, **kwargs):
raise NotImplementedError
def do_a_thing(self, stuff):
return self._method_that_does_io(stuff)
and then create a SyncAPI and AsyncAPI respectively in different files, each subclassing BaseApi implementing the respective io-bound helper-methods.
So Python 2 people can do:
from SomeAPI.sync import SomeAPI
and Python 3 people can run:
from SomeAPI.py3async import SomeAPI
The synchronous code is written in such a manner it's both 2 and 3 compatible, so every flavor can be happy.
- Xophmeister 8y agoBut how can that work when the async methods have a different signature and calling convention to their sync counterparts? Your “public” method in the async version would have to handle all the async set up and submission of your chain of coroutines to the loop. That’s possible, but it would have to live in isolation (i.e., no other async code could run simultaneously, which kind of defeats the purpose) and it means for a lot of nasty plumbing, which doesn’t seem like it should be the class’ job (in a separation of responsibilities sense).
- spamizbad 8y agoOh you can do it with identical signatures. Let’s say we are making http requests, with the sync code using requests and the async code using aiohttp. Your public methods all call a private _request method with a verb, a url, and optionally something to be seriaized to json. Your async _request method does need to do a small bit of code around sessions: make one first if one hasn’t been made for the class instance yet (or override your __init__ and take an existing one as an arg) but at the end of the day it’s just returning a coroutine that’ll ultimately give you a deserialized response. Sync can just be more direct and return a dict. Both have the same signature.
- Xophmeister 8y agoRight, so you can just return a coroutine, so the return type of your method is something like `Union[dict, Coroutine]`. However, then downstream (ultimately) has to handle the dichotomy, where the `dict` is as is, but the coroutine needs to be `await`ed. You can do it, but I personally think it's messy. It's what I really dislike about Python's async model, which you almost necessarily encounter because the stdlib is mostly not async.