async-cast 0.6.2
Cast an async function to blocking function and viceversa. Also use threads pools if needed.
License: MIT
Keywords: asyncio, async, sync, casting, threads

async_cast
==========

|.github/workflows/tests.yml|

Cast async function to blocking and viceversa. (works on python 3.7 and
up) Also run functions in threads, whether async or blocking.

Why ``async_cast``?
-------------------

I found the current ``asyncio`` tools frustrating when migrating from
blocking code. If you want to profit IO operations to *“run something
else”* you need to rewrite all code to use asyncio. When you start form
scratch that’s acceptable, but not with legacy code.

So with this small self-contained library you can easily convert legacy
code into async code. The best way is running several blocking function
in different threads. But **be aware** that threads also bring race
conditions, so make sure concurrent functions are thread-safe.

Installing
----------

https://pypi.org/project/async-cast/

::

   pip install -U async-cast

The package is a single module that you can easily audit.

Decorator: casting ``async`` function to a blocking function
------------------------------------------------------------

.. code:: python

   from async_cast import also_blocking

   @also_blocking
   async def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   if __name__ == '__main__':
       print(request_url.blocking('https://github.com'))

Decorator: casting a blocking function to ``async`` function
------------------------------------------------------------

.. code:: python

   from async_cast import also_async
   import asyncio

   @also_async
   def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   async def main():
       print(await request_url.async_('https://github.com'))

   if __name__ == '__main__':
       asyncio.run(main())

Running tasks in a ThreadPool
-----------------------------

I wrapped existing ``ThreadPoolExecutor`` to make it easier to run tasks
inside it. Tasks are automatically registered in the pool declared by
the ``with thread_pool(...):`` context.

Running ``async`` function in threadpool
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

   from async_cast import also_blocking, thread_pool
   import asyncio

   @also_blocking
   async def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   async def main():
       with thread_pool(3):
           t1 = request_url.async_thread('https://github.com')
           t2 = request_url.async_thread('https://google.com')
           t3 = request_url.async_thread('https://facebook.com')
           results = await asyncio.gather(t1,t2,t3)
           print(results)

   if __name__ == '__main__':
       asyncio.run(main())

Running blocking function in threadpool
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

   from async_cast import also_async, thread_pool
   import asyncio

   @also_async
   def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   async def main():
       with thread_pool(3):
           t1 = request_url.async_thread('https://github.com')
           t2 = request_url.async_thread('https://google.com')
           t3 = request_url.async_thread('https://facebook.com')
           results = await asyncio.gather(t1,t2,t3)
           print(results)

   if __name__ == '__main__':
       asyncio.run(main())

Non decorators alternatives
---------------------------

Casting ``async`` function to a blocking function with ``to_blocking``
----------------------------------------------------------------------

.. code:: python

   from async_cast import to_blocking

   async def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   if __name__ == '__main__':
       print(to_blocking(request_url)('https://github.com'))

Casting a blocking function to ``async`` function with ``to_async``
-------------------------------------------------------------------

.. code:: python

   from async_cast import to_async
   import asyncio

   def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   async def main():
       print(await to_async(request_url)('https://github.com'))

   if __name__ == '__main__':
       asyncio.run(main())

Running ``async`` or blocking function in threadpool with ``to_async_thread``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

   from async_cast import to_async_thread, thread_pool
   import asyncio

   async def request_url(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   def request_url_blocking(url, **kwargs):
       print(f'Requesting {url} with options {kwargs}')
       ...
       result = f'<h1>{url}</h1>'
       return result

   async def main():
       with thread_pool(3):
           t1 = to_async_thread(request_url)('https://github.com')
           t2 = to_async_thread(request_url)('https://google.com')
           t3 = to_async_thread(request_url)('https://facebook.com')
           t4 = to_async_thread(request_url_blocking)('https://duckduckgo.com')
           results = await asyncio.gather(t1,t2,t3,t4)
           print(results)

   if __name__ == '__main__':
       asyncio.run(main())

.. |.github/workflows/tests.yml| image:: https://github.com/joaduo/async_cast/actions/workflows/tests.yml/badge.svg
   :target: https://github.com/joaduo/async_cast/actions/workflows/tests.yml