Asyncer

repository·main·Indexed 25 days ago

https://github.com/fastapi/asyncer

A utility library built on AnyIO to improve the developer experience when working with async/await and concurrent code. It provides tools like asyncify() to run blocking sync code in worker threads, runnify() and syncify() to call async functions from sync contexts, and a TaskGroup with soonify() for managing concurrent tasks and their return values via SoonValue. The library focuses on type safety, mypy support, and editor autocompletion.

Tokens
4.6K
Snippets
8
Records
33
Agent score
81%

What's inside asyncer

  1. Get typing support with `asyncify()`

    main

    asyncify() is designed to preserve typing information. When you wrap a synchronous function, you receive full autocompletion and inline error support for:

    1. The arguments passed to the original function.
    2. The return value of the original function.

    This support extends to static analysis tools like mypy, helping to catch bugs and ensure type correctness when mixing sync and async code.

  2. Typing support for `SoonValue` objects

    main

    Asyncer provides full typing support for SoonValue objects. The .value attribute correctly inherits the return type of the function passed to soonify().

    This enables:

    • Autocompletion: Editors can suggest methods and attributes for the returned value.
    • Inline Errors: Type checkers will flag incorrect usage of the returned value.
    • Type Inference: Tools like mypy can infer types downstream in your application logic without explicit type annotations on every variable.
  3. How syncify() and asyncify() work together

    main

    In complex scenarios where sync and async code are interleaved, asyncer provides two complementary utilities:

    1. asyncify(func)(*args, **kwargs): Used to call synchronous code from an asynchronous context. It runs the sync function in a separate worker thread so it doesn't block the async event loop.
    2. syncify(func)(*args, **kwargs): Used to call asynchronous code from a synchronous context. It schedules the async function to run in the main async thread and returns the result to the sync caller.

    Example Workflow:

    • anyio.run() starts an async main() function.
    • main() calls await asyncify(do_sync_work)().
    • asyncify runs do_sync_work() in a worker thread.
    • Inside that worker thread, do_sync_work() calls syncify(do_async_work)().
    • syncify sends the task back to the main thread to run the async code, then brings the result back to the worker thread.
  4. Avoid blocking the event loop when calling sync code from async code

    main

    When running asynchronous code, calling a synchronous (blocking) function directly inside an async def function will block the event loop. This prevents the event loop from switching between other concurrent async tasks, wasting computation time and making the application unresponsive.

    The Problem (Blocking):

    import time
    
    def do_sync_work():
        time.sleep(1)
    
    async def main():
        do_sync_work()  # 😱 This blocks the entire event loop!
  5. Optimize performance when using syncify in sync programs

    main

    When using syncify(raise_sync_error=False) in a mainly synchronous program, each call triggers anyio.run(), which incurs the overhead of starting a new event loop.

    If you need to execute multiple async operations in a loop within a sync program, do not call syncify(raise_sync_error=False) inside the loop. Instead, wrap the entire loop in a single async function and call that single async function once. This ensures only one event loop is started.

  6. Call async code from sync code with syncify()

    main

    When you are inside a synchronous (blocking) function and need to call an asynchronous function, you cannot use await. Instead, use asyncer.syncify() to call the async function in a way that is compatible with synchronous code.

    syncify() works by sending the async function to be executed in the main thread (where the async event loop is running) and then returning the result back to the synchronous context (e.g., a worker thread) without requiring the caller to use await.

  7. Run async functions with arguments using runnify()

    main

    Use asyncer.runnify() to execute an asynchronous function that requires arguments.

    asyncer.runnify() accepts the async function as its first argument and returns a new function. This returned function accepts the same positional and keyword arguments as the original async function. Under the hood, it uses anyio.run() to manage the event loop.

    Key benefits include:

    • Typing Support: Provides excellent editor autocompletion for the arguments of the wrapped function.
    • Error Detection: Enables inline editor errors for incorrect argument types.
    • Static Analysis: Full support for mypy.
  8. Write and run basic async code with AnyIO

    main

    Asyncer is built on top of AnyIO. To write asynchronous Python code, you define functions using async def and use the await keyword to call them. Because async functions cannot be called directly from a synchronous context, you must use anyio.run() to execute the top-level entry point. This handles the event loop and waits for the function to complete.

    Key rules for async code:

    • You can only use await inside async functions.
    • To call an async function, you must use await in front of it.
  9. Use `asyncify()` to run sync code in a worker thread

    main

    To run synchronous, blocking code from within an async context without blocking the event loop, use asyncer.asyncify().

    asyncify() takes a synchronous function and returns a new async function. When you await this new function, asyncer (via AnyIO) executes the original function in a separate worker thread. This allows the event loop to continue running other tasks while the sync work is being performed.

    Example Usage:

    import time
    from asyncer import asyncify
    
    def do_sync_work():
        time.sleep(1)
    
    async def main():
        # asyncify returns an async function that accepts the original function's arguments
        await asyncify(do_sync_work)() 
  10. Install Asyncer using uv

    main

    To set up a new project with Asyncer using uv, initialize a bare project and add the package. This will create a virtual environment in .venv, add Asyncer to pyproject.toml, and generate a uv.lock file to ensure reproducible dependency versions.

    $ uv init awesome-project --bare
    $ cd awesome-project
    $ uv add asyncer
  11. Install the Asyncer AI Agent Skill

    main

    Asyncer provides an official skill for AI coding agents (such as Claude Code, Cursor, or GitHub Copilot) to ensure agent guidance stays aligned with your installed version.

    After installing Asyncer in your project, run the following command using uvx (an alias for uv tool run) to install the skill via Library Skills. This runs in an isolated environment while scanning your project's installed packages.

    Note for Claude Code users: When prompted for an installation location, select .claude/skills.