Skip to main content

Retrofit concurrency into synchronous Python code: start work in the background now, block only when the result is actually used.

Project description

autoasync

Wrap synchronous work so it starts in the background and returns immediately.
The call only blocks when you actually use the result.

from autoasync import autoasync


def load_report(path):
    ...


load_report_async = autoasync(load_report)
report = load_report_async("report.csv")

prepare_page()                    # runs while load_report works in background
print(report)                     # blocks here only if the result is not ready yet

Why use this instead of async / await?

autoasync is mainly for retrofitting concurrency into existing synchronous code. Compared with Python's built-in async / await, its main advantages are:

  • No async contagion: callers can stay synchronous, so you do not need to turn the whole call chain into async def just to overlap one slow step.
  • Minimal code changes: wrapping a function with @autoasync or autoasync(fn) is often enough to start work in the background.
  • Deferred waiting: you do not have to decide upfront where to await; execution only blocks when the value is actually needed.
  • Easy incremental optimization: this is convenient when improving a mature sync codebase, because you can add concurrency without redesigning APIs around an event loop.

That trade-off is intentional: this abstraction adds overhead, so it is not a replacement for native async / await in high-throughput async systems. It is most useful when you want a simple, low-friction way to hide latency in otherwise synchronous code.

Install

pip install autoasync

Usage

Decorate any synchronous function

from autoasync import autoasync


@autoasync
def fetch(url: str) -> str:
    import requests
    return requests.get(url).text


result = fetch("https://example.com")
do_other_work()
print(result)

Run several calls concurrently

a = fetch("https://example.com/a")
b = fetch("https://example.com/b")
c = fetch("https://example.com/c")

combined = a + "\n" + b + "\n" + c

CPU-bound work with processes

use_process=True is only supported for importable module-level functions.

from autoasync import autoasync


@autoasync(use_process=True)
def crunch(n: int) -> int:
    return sum(range(n))


result = crunch(10_000_000)
do_other_work()
print(result)

Custom executor

from concurrent.futures import ThreadPoolExecutor
from autoasync import autoasync

pool = ThreadPoolExecutor(max_workers=4)


@autoasync(executor=pool)
def read(path: str) -> str:
    with open(path) as f:
        return f.read()

Configure built-in executors globally

Use configure_autoasync(...) when you want to keep the library-managed executors but control their default sizes.

from autoasync import autoasync, configure_autoasync

configure_autoasync(thread_max_workers=8, process_max_workers=4)

@autoasync
def fetch(url):
    ...


@autoasync(use_process=True)
def crunch(n):
    return sum(range(n))


page = fetch("https://example.com")   # uses the configured thread pool
total = crunch(10_000_000)            # uses the configured process pool

Configuration changes apply to future built-in pools only. Existing cached pools are not replaced. If you pass a custom executor, the global built-in pool configuration is ignored.

Use reset_autoasync() to clear cached built-in pools and restore default configuration, which is especially useful in tests.

Access the original function

@autoasync
def add(a, b):
    return a + b


add.__wrapped__(1, 2)

What the returned value supports

The returned value behaves like the final result for most common Python protocols, including:

  • attribute access
  • arithmetic and comparisons
  • container access and iteration
  • conversions such as int, float, str, bytes, and bool
  • context managers
  • open() path usage through __fspath__

The background result is resolved once and then cached.

Important behavior

is is special

is checks object identity and cannot be overloaded in Python. That means:

resolve_true = autoasync(lambda: True)
result = resolve_true()

result == True         # works
bool(result) is True   # works
result is True         # False: `is` checks identity, not the eventual value

If you need identity-style checks, resolve first or use == / bool(...).

Exceptions are deferred

If the wrapped function raises an exception, the exception is not raised at call time.
It is re-raised when the result is first needed.

@autoasync
def explode():
    raise RuntimeError("boom")


result = explode()     # no exception yet
print(result)          # raises RuntimeError here

Process mode restrictions

When use_process=True, the wrapped function must be:

  • defined at module scope
  • importable from its module
  • a normal function, not a lambda, nested function, bound method, or callable object
  • optionally paired with configure_autoasync(process_max_workers=...) to size the built-in process pool

Unsupported targets raise a clear TypeError.

How it works

result = slow_fn(arg)          # submits fn(arg) to an executor
                               # returns immediately

do_other_work()                # runs concurrently with fn(arg)

print(result + 1)              # blocks only here if needed

Requirements

  • Python 3.8+
  • No runtime dependencies

License

MIT

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

autoasync-0.1.3.tar.gz (14.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

autoasync-0.1.3-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file autoasync-0.1.3.tar.gz.

File metadata

  • Download URL: autoasync-0.1.3.tar.gz
  • Upload date:
  • Size: 14.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for autoasync-0.1.3.tar.gz
Algorithm Hash digest
SHA256 f5a5c9dd280d1e1c5910bc5e1dcb6e064494ab4530cc62c305bf082a47690005
MD5 3d16c0c1189e485f16313165018cf817
BLAKE2b-256 cd9478b0b92123b33ad7a7bfe206e8af29a2e05f69594ab29ebfa09c2441a3b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for autoasync-0.1.3.tar.gz:

Publisher: ci.yml on elyar-adil/autoasync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file autoasync-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: autoasync-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 9.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for autoasync-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 140d8e7f2d74c624ae40367adc5f65c45e5581ba9315cc839c3160c4f34f631f
MD5 111dcccbf26f36c2af57b26726ff56e4
BLAKE2b-256 0fa28a03ee2b1363794b2bb8e9c9a5f20f65af204738094eacd14d58a1b9b43b

See more details on using hashes here.

Provenance

The following attestation bundles were made for autoasync-0.1.3-py3-none-any.whl:

Publisher: ci.yml on elyar-adil/autoasync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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