Skip to main content

asynclit

Read the Docs PyPI version Python versions License

Documentation: asynclit.readthedocs.io

Small async task layer for Streamlit (and similar “sync main thread + rerun” UIs): run sync or async work on a dedicated background event loop, then poll status, results, progress, and cancellation without blocking the UI thread.

Install

pip install asynclit

Optional extras:

  • Streamlit (for a typical app environment): pip install 'asynclit[streamlit]'
  • APScheduler (optional timers/jobs): pip install 'asynclit[scheduler]'

Requires Python 3.9+.

Quick start

import streamlit as st
import asynclit

task = asynclit.run(fetch_data)

if task.done:
    st.write(task.result)
else:
    st.write("Loading…")

On each rerun, check task.done and read task.result when finished.

Example output (from the included test app tests/streamlit_apps/asynclet_poll_app.py via Streamlit AppTest):

run 0 ['wait']
run 1 ['ready:138']
... last ['ready:138']

Public API

Item Role
asynclit.run(func, /, *args, manager=None, retry=None, **kwargs) Submit func on the worker; returns a Task.
Task.done Whether the result (or error) is ready.
Task.result Result value; raises if not complete.
Task.status TaskStatus: PENDING, RUNNING, DONE, ERROR, CANCELLED.
Task.error Exception object when status is ERROR, else None.
Task.cancel() Request cancellation (running tasks use asyncio cancellation; pending tasks cancel the result future).
Task.progress Non-blocking drain of progress values (see below).
TaskManager / get_default_manager() Custom registry and cleanup() when you keep many completed tasks.
session_tasks(session_state) Dict stored on st.session_state for named tasks.
RetryPolicy Retry configuration for exception-based retries.
schedule_interval(...) / schedule_cron(...) Schedule periodic task submissions (requires asynclit[scheduler]).

Progress (Janus)

Progress is supported for async functions only.

Declare a parameter named queue or progress_queue:

  • If it is the first parameter, asynclit injects the Janus queue positionally and the remaining positional arguments to run() map to the rest of the signature.
  • Otherwise, asynclit injects the queue by keyword (queue= / progress_queue=).
async def job(queue, steps: int):
    for i in range(steps):
        await queue.async_q.put(i)
    return steps

task = asynclit.run(job, 10)
# Each rerun:
for x in task.progress:
    st.write(x)

The UI thread reads via task.progress, which pulls from the sync side of a janus queue.

Streamlit session state

import streamlit as st
import asynclit

tasks = asynclit.session_tasks(st.session_state)
if "load" not in tasks:
    tasks["load"] = asynclit.run(load_data)

task = tasks["load"]

Patterns

Named tasks (per session)

Use session_tasks(st.session_state) as a stable dict to store tasks across reruns:

tasks = asynclit.session_tasks(st.session_state)

if "load" not in tasks:
    tasks["load"] = asynclit.run(load_data)

task = tasks["load"]

Cleanup (when you create many tasks)

If you create many tasks over time, keep them in a TaskManager and periodically call cleanup() to trim completed entries:

m = asynclit.TaskManager(max_completed=256)
task = asynclit.run(load_data, manager=m)

# ... later:
m.cleanup()

Errors

If the callable raises, task.status becomes ERROR, task.error holds the exception, and reading task.result re-raises it.

if task.status == asynclit.TaskStatus.ERROR:
    st.error(f"failed: {task.error!r}")
elif task.done:
    st.write(task.result)
else:
    st.write("Loading…")

Cancellation

task.cancel() requests cancellation:

  • If the task is running, it schedules asyncio cancellation on the worker loop.
  • If the task is still pending (not yet bound on the worker loop), it cancels the result future.

Treat CANCELLED as a terminal state in UI code.

Cooperative cancellation patterns

  • Async jobs: rely on asyncio cancellation; add cancellation checkpoints when doing long CPU work (break work into chunks; await occasionally).
  • Sync jobs: cancellation is best-effort; the underlying threadpool work may continue running. Prefer chunked work that you can stop between chunks.

Retries

Use RetryPolicy for exception-based retries (opt-in per run() / TaskManager.submit()).

policy = asynclit.RetryPolicy(
    max_attempts=3,
    base_delay=0.1,
    max_delay=1.0,
    multiplier=2.0,
    jitter=0.0,
)

task = asynclit.run(fetch_data, retry=policy)

Retries stop when attempts are exhausted, the task is cancelled, or a raised exception does not match the policy.

Scheduling (APScheduler)

Install with:

pip install 'asynclit[scheduler]'

Then schedule periodic task submission on the worker loop:

asynclit.schedule_interval(load_data, seconds=60, latest_task_name="load_data")

# Later (poll the most recent task):
m = asynclit.get_default_manager()
latest = m.get("global:load_data")
if latest and latest.done:
    st.write(latest.result)

Troubleshooting / FAQ

Why does it keep showing wait?

In rerun-driven UIs, a single script run may finish before the background task completes. The usual pattern is: show wait, then on the next rerun read task.done / task.result.

In tests (or special cases), you may need to allow a small amount of wall time between reruns for the worker to finish.

How it works (short)

  • One daemon thread runs a single asyncio event loop.
  • Async callables run on that loop; sync callables run via asyncer’s asyncify (thread pool).
  • Submissions use asyncio.run_coroutine_threadsafe; results are bridged with a concurrent.futures.Future for the polling API.

Development

pip install -e '.[dev]'
pytest

Development (uv)

If you use uv, you can run tests in a fresh env like:

uv venv
uv pip install -e '.[dev]'
uv run pytest

The dev extra includes Streamlit so CI can run headless AppTest checks in tests/test_streamlit_apptest.py against the sample apps under tests/streamlit_apps/.

Docs

Build the documentation (Sphinx + MyST):

uv venv
uv pip install -e '.[docs]'
uv run python -m sphinx -b html docs docs/_build/html

License

MIT.

Metadata

Release files for asynclit 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for asynclit 0.2.2
File Size Uploaded
asynclit-0.2.2.tar.gz 12.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for asynclit 0.2.2
File Interpreter ABI Platform
asynclit-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 26.7 kB

Release files / asynclit-0.2.2.tar.gz

Download URL asynclit-0.2.2.tar.gz
Size 12.0 kB
Tags Source
SHA-256 checksum
How to use checksums
94034ced83598f22b1cebf42554110b6da6ba224f1003e9296f04e0dfa733cf4
BLAKE2b-256 checksum
How to use checksums
1292bd4d5c2d280344ff0c1001ce5b070e16954ece3434ce6c70abe146c42d2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / asynclit-0.2.2-py3-none-any.whl

Download URL asynclit-0.2.2-py3-none-any.whl
Size 14.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
20557c8d414fee7ac3009a61ce095dff0342730653d9c55de477f1b73cfaba19
BLAKE2b-256 checksum
How to use checksums
096cf8e1f36be8c199e9901830f320308eaf6166a54b8d7ae35043cd7eb37a79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page