Skip to main content

Run async functions seamlessly from sync code using a persistent background event loop.

Project description

palitra

A lightweight bridge between synchronous and asynchronous Python code, maintaining a persistent event loop in a background thread. It allows you to call async def functions directly from regular (sync) code without blocking or complex event loop reentry.

Unlike asyncio.run(), which creates and tears down a new event loop on each call, using palitra.run() eliminates that overhead — preserving async state and resources (like aiohttp sessions or database connections) across multiple calls.

a.k.a. "palette" — captures the essence of the library: blending differently colored (sync/async) functions like on an artist’s palette.

⚠️ Known issues: Unexpected behaviour in 3.13t build.

If something breaks in your environment, please report an issue—the whole purpose of this library is to spare developers from reinventing async/sync bridges in every project. Your feedback directly helps improve its reliability and real-world compatibility.

Inspired by Running async code from sync in Python asyncio by lemon24 and related discussions such as Celery #9058.

Installation

You can install palitra from PyPI with pip or any other Python package manager (uv, poetry, etc):

pip install palitra

Features

  • ✅ Runs a persistent asyncio event loop in a background thread
  • ✅ Simple, thread-safe API for running coroutines from sync code
  • ✅ No monkey patching or global loop overrides
  • ✅ Automatic cleanup via atexit and weakref to global runner (if used)
  • ✅ Lightweight: no external dependencies

Documentation

Why does this even exist?

Usage Examples

This is not ideal, but in real-world scenarios, migrating to ASGI isn’t always possible. When stuck with WSGI, palitra lets you still use async features to get things working.

Flask with aiohttp

from flask import Flask, jsonify
import palitra
import aiohttp
import asyncio

app = Flask(__name__)

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.json()

@app.route('/api/comments')
def get_comments():
    async def fetch_all():
        async with aiohttp.ClientSession() as session:
            urls = [
                'https://jsonplaceholder.typicode.com/comments/1',
                'https://jsonplaceholder.typicode.com/comments/2',
                'https://jsonplaceholder.typicode.com/comments/3',
            ]
            return await asyncio.gather(*[fetch_url(session, url) for url in urls])

    comments = palitra.run(fetch_all())
    return jsonify(comments)

if __name__ == '__main__':
    app.run()

Celery

import palitra
from celery import Celery
import asyncio
import time

celery_app = Celery('tasks', broker='pyamqp://guest@localhost//')

async def async_processing(data: str) -> dict:
    await asyncio.sleep(0.5)  # simulate async I/O
    return {"input": data, "processed": True, "timestamp": time.time()}

@celery_app.task(name="process_async")
def sync_celery_wrapper(data: str):
    return palitra.run(async_processing(data))

Contributing

Pull requests are welcome! Please:

  • Document known issues or caveats
  • Include test coverage for new features
  • Keep the code as simple and minimal as possible
  • Prefer clarity over cleverness

Things that need more work:

  • Proper stress testing
  • Verifying thread safety in edge cases
  • Detecting and eliminating memory leaks
  • Ensuring reliable shutdown under all conditions

License

BSD-3-Clause

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

palitra-0.0.1.post1.tar.gz (7.4 kB view details)

Uploaded Source

Built Distribution

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

palitra-0.0.1.post1-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file palitra-0.0.1.post1.tar.gz.

File metadata

  • Download URL: palitra-0.0.1.post1.tar.gz
  • Upload date:
  • Size: 7.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for palitra-0.0.1.post1.tar.gz
Algorithm Hash digest
SHA256 9faac26fc232706b99a87823b82cf5f63c533576799b17d6c835a49d0e2539de
MD5 d7a6ec8027657bf8622b0ca5e0ed90e2
BLAKE2b-256 a981c825f50ffd7efa9beb59d36b537117c9f3cb6b621dfc83135303579b0cb6

See more details on using hashes here.

Provenance

The following attestation bundles were made for palitra-0.0.1.post1.tar.gz:

Publisher: ci.yml on abebus/palitra

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

File details

Details for the file palitra-0.0.1.post1-py3-none-any.whl.

File metadata

  • Download URL: palitra-0.0.1.post1-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for palitra-0.0.1.post1-py3-none-any.whl
Algorithm Hash digest
SHA256 a26a67e0ffe16022d4108490a674634ae5c42c555c40edcf2c863d9795dcad7b
MD5 86794537a7ca53ba4e966da6931da37a
BLAKE2b-256 eac151066866e83f9e390a4c17a5538faa69c7ee4f2b60b60554663e9c946574

See more details on using hashes here.

Provenance

The following attestation bundles were made for palitra-0.0.1.post1-py3-none-any.whl:

Publisher: ci.yml on abebus/palitra

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