Overview
The Problem: Sync and Async Don’t Mix
Python’s async/await brings powerful concurrency, but creates a fundamental incompatibility:
Sync code cannot call async functions - You can’t
awaitin regular functionsAsync code cannot call sync functions safely - Blocking calls freeze the event loop
Libraries must choose one or the other - Forcing users into specific execution contexts
This forces developers to:
Maintain separate sync and async versions of the same code
Rewrite entire codebases when adopting async
Abandon useful libraries that don’t match their execution context
Build complex wrapper layers to bridge the gap
What is SmartAsync?
SmartAsync automatically bridges sync and async Python code.
It’s a decorator that makes functions work seamlessly in both synchronous and asynchronous contexts:
Write your function once (sync or async)
Use it everywhere (sync or async context)
Zero configuration - just add
@smartasyncAutomatic context detection at runtime
from smartasync import smartasync
# Write once - async version
@smartasync
async def fetch_data(url: str):
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
# Use in sync context - works automatically!
def sync_main():
data = fetch_data("https://api.example.com/users")
print(data)
# Use in async context - works automatically!
async def async_main():
data = await fetch_data("https://api.example.com/users")
print(data)
When is SmartAsync Useful?
1. Using Async Libraries in Sync Applications
You have a sync application but want to use modern async libraries (httpx, aiofiles, databases):
# Your existing sync application
def process_users():
for user_id in user_ids:
# Use async httpx library seamlessly
data = fetch_user(user_id) # Actually calls async function
save_to_db(data)
2. Building Libraries with Unified APIs
You’re building a library and want users to use it in both sync and async code:
# Your library code - write once
@smartasync
async def query_database(sql: str):
async with database.connect() as conn:
return await conn.execute(sql)
# Users can use it either way:
result = query_database("SELECT * FROM users") # Sync
result = await query_database("SELECT * FROM users") # Async
3. Gradual Migration to Async
You’re migrating from sync to async but can’t rewrite everything at once:
# Old sync code
def process_order(order_id):
order = get_order(order_id) # Still sync
payment = charge_payment(order) # Already migrated to async
send_email(order.email) # Still sync
# Migrated function works in sync context
@smartasync
async def charge_payment(order):
async with stripe.Client() as client:
return await client.charge(order.amount)
4. Testing Async Code Synchronously
You want to test async code without dealing with event loops in tests:
@smartasync
async def fetch_user(user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"/users/{user_id}")
return response.json()
# Simple sync test - no event loop needed
def test_fetch_user():
user = fetch_user(123)
assert user["id"] == 123
assert "name" in user
5. Plugin Systems
You’re building a plugin system where plugins might be sync or async:
from smartswitch import Switcher
plugins = Switcher()
# Some plugins are async
@plugins
@smartasync
async def email_plugin(data):
async with aiosmtplib.SMTP() as smtp:
await smtp.send_message(data)
# Some plugins are sync
@plugins
def log_plugin(data):
logger.info(data)
# Your framework calls them uniformly
def execute_plugins(data):
for name in plugins._handlers:
result = plugins(name)(data) # Works for both!
6. Interactive Environments (Jupyter, IPython)
You want async functions to work in interactive shells without explicit await:
@smartasync
async def explore_api(endpoint):
async with httpx.AsyncClient() as client:
return await client.get(endpoint)
# In Jupyter/IPython - just call it
>>> data = explore_api("/api/users")
>>> print(data)
When NOT to Use SmartAsync
Don’t use SmartAsync if:
Performance is critical - There’s overhead in context detection (~microseconds)
You’re only in one context - If you’re purely async or purely sync, use native code
You need fine-grained control - SmartAsync abstracts away execution details
How It Works
SmartAsync detects the execution context at runtime:
- In Sync Context:
Async functions run in a new event loop using
asyncio.run()Sync functions run normally
- In Async Context:
Async functions run directly with
awaitSync functions run in a thread pool (non-blocking)
- Smart Caching:
Detects which wrapper to use once per context
Subsequent calls skip detection overhead
Automatic cache invalidation on context switch
Key Features
✅ Automatic Context Detection - No configuration needed
✅ Bidirectional - Supports sync→async and async→sync
✅ Thread-Safe - Sync code in async context runs in thread pool
✅ Zero Dependencies - Pure Python standard library
✅ Type Safe - Full type hints support
✅ Performance Optimized - Smart caching minimizes overhead
Next Steps
Ready to try SmartAsync?
Installation - Install SmartAsync
Quick Start - Get started in 5 minutes
Usage Scenarios - See real-world usage patterns
SmartSwitch Integration - Use with SmartSwitch for powerful dispatching