Simple decorator for executing an asynchronous Python callable in a synchronous context.
Project description
syncable
syncable is a Python decorator that allows you to call asynchronous functions from both synchronous and asynchronous contexts seamlessly. This is especially useful for library authors and application developers who want to provide a unified API, regardless of whether the caller is in an async or sync context.
Features
- Call async functions from sync code without worrying about event loops.
- Works transparently in async, sync, and AnyIO worker thread contexts.
- Preserves context variables (
contextvars) across thread boundaries. - Simple, lightweight, and dependency-minimal (only requires
anyio).
Installation
pip install syncable
Usage
Basic Example
Suppose you have an async function:
import asyncio
async def fetch_data():
await asyncio.sleep(1)
return "data"
With syncable, you can decorate it:
from syncable import syncable
@syncable
async def fetch_data():
await asyncio.sleep(1)
return "data"
Now you can call fetch_data() from both sync and async code:
From Synchronous Code
result = fetch_data()
print(result) # "data"
From Asynchronous Code
async def main():
result = await fetch_data()
print(result) # "data"
import asyncio
asyncio.run(main())
How It Works
- Async context: Returns the coroutine for you to
await. - Sync context: Runs the coroutine in a background event loop and blocks until the result is ready.
- AnyIO worker thread: Uses AnyIO's thread portal to run the coroutine in the main event loop.
Advanced Example: Preserving Context Variables
import contextvars
from syncable import syncable
user_var = contextvars.ContextVar("user")
@syncable
async def whoami():
return user_var.get()
def sync_caller():
user_var.set("alice")
print(whoami()) # prints "alice"
import asyncio
async def async_caller():
user_var.set("bob")
print(await whoami()) # prints "bob"
sync_caller()
asyncio.run(async_caller())
Accessing the Original Async Function
If you need the undecorated async function, use the .aio attribute:
@syncable
async def foo(): ...
foo.aio # This is the original async function
Limitations
- Async generators are not supported and will raise an error if decorated.
- Relies on AnyIO internals for worker thread detection (may break with future AnyIO versions).
- In rare edge cases (e.g., nested event loops in Jupyter), behavior may vary.
Logging
syncable uses Python's standard logging. To see debug logs, configure logging in your application:
import logging
logging.basicConfig(level=logging.DEBUG)
License
MIT
Contributing
Pull requests and issues are welcome!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file syncable-0.2.0.tar.gz.
File metadata
- Download URL: syncable-0.2.0.tar.gz
- Upload date:
- Size: 5.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0b1fe217a594bdcaa0d25990e979eb1e4c35c99a3c21608dc8cddc86b3a3ee9
|
|
| MD5 |
09b390e393418966fbc84218bedf2f12
|
|
| BLAKE2b-256 |
143ab5e24809e377955ff423d7f983f75d26a59f0e9b45ad23b24e322587d0ad
|
File details
Details for the file syncable-0.2.0-py3-none-any.whl.
File metadata
- Download URL: syncable-0.2.0-py3-none-any.whl
- Upload date:
- Size: 5.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04aaad44b92573be08aae3b0d4f4abb506c7c8a7a8f30e26bae06f88afb805ab
|
|
| MD5 |
db875c5f0a370dc7d21824fd5d9d925d
|
|
| BLAKE2b-256 |
9de47a007566c3a3516f94140384e408a7cdf90e8cfeca507720ee9141571692
|