Let's assume I have a class that is used to perform I/O operations:
class CommunicationStack:
def __init__(self, socket):
self.socket = socket
def sync_get_data(self):
...
def sync_send_data(self):
...
async def async_get_data(self):
...
async def async_send_data(self):
...
As you can see it has sync and async variant for the same operations but it would be slightly inconvenient to write async_get_data
or sync_get_data
manually. I am looking for a smart way to have the same interface like
def get_data(self):
... # call sync variant or return an awaitable, depending on caller type
def send_data(self):
... # call sync variant or return an awaitable, depending on caller type
so it can be conveniently used like:
stack = CommunicationStack(...)
def thread_procedure():
data = stack.get_data() # get_data returns data
async def task_procedure():
data = await stack.get_data() # get_data returns an awaitable
I believe it can be done in some tricky way with inspections or some black magic:
def is_caller_coroutine():
return sys._getframe(2).f_code.co_flags & 0x380
to check if caller is a coroutine or a function but it seems like a bad design to mess with python's guts.
The question is: what is a good way to choose appropriate variant? Or is there a better way to design everything like using adapters or developing two independent AsyncCommunicationStack
and SyncCommunicationStack
classes?