DEV Community

MrNaif2018
MrNaif2018

Posted on

Universalasync – create sync and async libraries at the same time maintaining one codebase

I would like to show my new Python library universalasync: https://github.com/bitcartcc/universalasync

The problem

Async/await is become more and more popular in python and it makes sense to use it quite often to do something useful when waiting on network I/O. But it is not possible to use blocking (sync) libraries as it would block the whole event loop and defeat the purpose of asynchronous programming. So specialized libraries need to be created. In the same time, async is not always required, so a sync interface is demanded often for all the libraries.
For the library maintainers (like me) it causes an issue what to do. Often we need to maintain two codebases which differ only a little bit.

Some libraries create async_* variants of functions, or create classes like AsyncClient or use other methods of creating sync versions (for example in my another library some time ago I was just removing async and await and publishing this as a separate package, mylib and mylib-async).
The solution

The universalasync library provides utilities to help library maintainers create so-called "universal" libraries. You need to create only asynchronous version of your library, and synchronous version will be created automatically, so that users could use it in their sync and async apps interchangeably.

The library has 0 dependencies of course, it wraps all public methods of a class to it's implementation. It basically does what you would have done if you needed to run a coroutine from your sync code, but automatically.

Example:

from universalasync import wrap

@wrap
class Client:
    async def help(self):
        ...

    @property
    async def async_property(self):
        ...

client = Client()

def sync_call():
    client.help()
    client.async_property

async def async_call():
    await client.help()
    await client.async_property

# works in all cases
sync_call()
asyncio.run(async_call())
threading.Thread(target=sync_call).start()
threading.Thread(target=asyncio.run, args=(async_call(),)).start()
Enter fullscreen mode Exit fullscreen mode

API reference can be found here: https://universalasync.bitcartcc.com/en/latest/api.html

Any feedback, testing and bugs found welcome!

Top comments (0)