Skip to main content

A lightweight library for allowing async functions to be called in a synchronous manner.

Project description

AnySync

A lightweight library for allowing async functions to be called in a synchronous manner.

from anysync import anysync


@anysync
async def f():
    return 42


assert f().run() == 42

Usage

Coroutines

The primary use case for anysync is to allow async functions to be called in a synchronous manner. All you need to do is add the anysync decorator to your async function:

import asyncio
from anysync import anysync


@anysync
async def f():
    return 42


def test_sync():
    assert f().run() == 42


async def test_async():
    assert await f() == 42


test_sync()
asyncio.run(test_async())

anysync works by wrapping coroutines returned by async functions in an AnySync object that can both be awaited and executed synchronously when calling its run() method.

from anysync import AnySync


async def f():
    return 42


coro = f()
assert AnySync(coro).run() == 42

Context Managers

You can even use AnySync on your async context managers.

import asyncio
from anysync import anysynccontextmanager


@anysynccontextmanager
async def cm():
    yield 42


def test_sync():
    with cm() as x:
        assert x == 42


async def test_async():
    async with cm() as x:
        assert x == 42


test_sync()
asyncio.run(test_async())

You can alternatively subclass the AnySyncContextManager class:

from anysync import AnySyncContextManager


class CM(AnySyncContextManager):
    async def __aenter__(self):
        return 42

    async def __aexit__(self, exc_type, exc, tb):
        pass


def test_sync():
    with CM() as x:
        assert x == 42


async def test_async():
    async with CM() as x:
        assert x == 42


test_sync()
asyncio.run(test_async())

Comparisons

asyncio.run

Unlike asyncio.run, an AnySync object can be run() even if an event loop is already running.

For example, the following code will raise a RuntimeError:

import asyncio


async def f():
    return 42


async def test_async():
    assert asyncio.run(f()) == 42


asyncio.run(test_async())

However, with AnySync, the following code will work as expected:

import asyncio
from anysync import anysync


@anysync
async def f():
    return 42


async def test_async():
    assert f().run() == 42


asyncio.run(test_async())

unsync

AnySync is similar to unsync in that it allows async functions to be called synchronously when needed. The main differences are that AnySync works with type checkers, is lighter weight, and works with other async libraries like trio and curio via anyio.

Automatic Detection

The other approach to dealing with the challenges of mixing synchronous and asynchronous code is to automatically infer whether a function should be run synchronously based on whether it is being run in an async context. This approach is taken by libraries like Prefect's sync_compatible decorator. The main downside is that the behavior of the function changes dynamically depending on the context which can lead to unexpected behavior.

For example, the code below works as expected beca

from prefect.utilities.asyncutils import sync_compatible


@sync_compatible
async def request():
    ...
    return "hello"


def work():
    response = request()
    ...
    return response.upper()


def test_sync():
    assert work() == "HELLO"


test_sync()

However, if we now call work() from an async context, the behavior changes.

import asyncio


async def test_async():
    assert work() == "HELLO"  # AttributeError: 'coroutine' object has no attribute 'upper'


asyncio.run(test_async())

Because work() is now being called from an async context, request() automatically returns a coroutine object which causes work() to fail.

Other Considerations

How it Works

AnySync works by detecting the presence of a running event loop. If one already exists, then AnySync spawns a thread and corresponding event loop to run the coroutine. Specifically, it has an underlying ThreadPoolExecutor whose max_workers can be configured by setting a ANYSYNC_THREAD_COUNT_MAX environment variable.

Interacting with contextvars

AnySync wrapped coroutines or context managers will not propagate changes to contextvars from async to synchronous contexts. This is because contextvars are not shared between threads or event loops and AnySync must create these in order to run coroutines synchronously. Given this, the following is not supported:

from contextvars import ContextVar
from anysync import anysync


var = ContextVar("var", default=0)


@anysync
async def f():
    var.set(42)


f().run()
assert var.get() == 42  # AssertionError: 0 != 42

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

anysync-0.2.1.tar.gz (5.8 kB view details)

Uploaded Source

Built Distribution

anysync-0.2.1-py3-none-any.whl (4.9 kB view details)

Uploaded Python 3

File details

Details for the file anysync-0.2.1.tar.gz.

File metadata

  • Download URL: anysync-0.2.1.tar.gz
  • Upload date:
  • Size: 5.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.27.0

File hashes

Hashes for anysync-0.2.1.tar.gz
Algorithm Hash digest
SHA256 d835de5ca6740dcea1567b0b1e504ab19169dd0c0ab4114a2fbc29171e129052
MD5 35b92773319992f90e93be74f38bbb3b
BLAKE2b-256 a5864dad1b5681cdd5b09d67564bf7adaec3f58f934a2776f86b8f3880ad2143

See more details on using hashes here.

File details

Details for the file anysync-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: anysync-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 4.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.27.0

File hashes

Hashes for anysync-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3f388fd7212c0fb3204d763db9ec47b2b6a0e697e1d35ca6534c7d6cd2243b54
MD5 48b583d6ee64ea1cdf740a4bf7652ea5
BLAKE2b-256 c5b8f126f0627c29cdb1a6a9223424547b80e04479ea7436d40a663c4304d90c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page