Skip to main content

Unified sync/async API decorator with automatic context detection

Project description

SmartAsync Logo

SmartAsync

Sync or async: this WAS the question

Unified sync/async API decorator with automatic context detection

PyPI version Tests codecov Documentation Status Python 3.10+ License: MIT Part of Genro-Libs LLM Docs

SmartAsync allows you to write async methods once and call them in both sync and async contexts without modification. It automatically detects the execution context and adapts accordingly.

Features

  • Automatic context detection: Detects sync vs async execution context at runtime
  • Zero configuration: Just apply the @smartasync decorator
  • Asymmetric caching: Smart caching strategy for optimal performance
  • Compatible with __slots__: Works with memory-optimized classes
  • Pure Python: No dependencies beyond standard library

Installation

pip install smartasync

Quick Start

from smartasync import smartasync
import asyncio

class DataManager:
    @smartasync
    async def fetch_data(self, url: str):
        """Fetch data - works in both sync and async contexts!"""
        async with httpx.AsyncClient() as client:
            response = await client.get(url)
            return response.json()

# Sync context - no await needed
manager = DataManager()
data = manager.fetch_data("https://api.example.com/data")

# Async context - use await
async def main():
    manager = DataManager()
    data = await manager.fetch_data("https://api.example.com/data")

asyncio.run(main())

How It Works

SmartAsync uses asyncio.get_running_loop() to detect the execution context:

  • Sync context (no event loop): Executes with asyncio.run()
  • Async context (event loop running): Returns coroutine to be awaited

Asymmetric Caching

SmartAsync uses an intelligent caching strategy:

  • Async context detected: Cached forever (can't transition from async to sync)
  • ⚠️ Sync context: Always rechecked (can transition from sync to async)

This ensures correct behavior while optimizing for the most common case (async contexts in web frameworks).

Use Cases

1. CLI + HTTP API

from smartasync import smartasync
from smpub import PublishedClass, ApiSwitcher

class DataHandler(PublishedClass):
    api = ApiSwitcher()

    @api
    @smartasync
    async def process_data(self, input_file: str):
        """Process data file."""
        async with aiofiles.open(input_file) as f:
            data = await f.read()
        return process(data)

# CLI usage (sync)
handler = DataHandler()
result = handler.process_data("data.csv")

# HTTP usage (async via FastAPI)
# Automatically works without modification!

2. Testing

@smartasync
async def database_query(query: str):
    async with database.connect() as conn:
        return await conn.execute(query)

# Sync tests
def test_query():
    result = database_query("SELECT * FROM users")
    assert len(result) > 0

# Async tests
async def test_query_async():
    result = await database_query("SELECT * FROM users")
    assert len(result) > 0

3. Mixed Codebases

Perfect for gradually migrating sync code to async without breaking existing callers.

Performance

  • Decoration time: ~3-4 microseconds (one-time cost)
  • Sync context: ~102 microseconds (dominated by asyncio.run() overhead)
  • Async context (first call): ~2.3 microseconds
  • Async context (cached): ~1.3 microseconds

For typical CLI tools and web APIs, this overhead is negligible compared to network latency (10-200ms).

Advanced Usage

With __slots__

SmartAsync works seamlessly with __slots__ classes:

from smartasync import smartasync

class OptimizedManager:
    __slots__ = ('data',)

    def __init__(self):
        self.data = []

    @smartasync
    async def add_item(self, item):
        await asyncio.sleep(0.01)  # Simulate I/O
        self.data.append(item)

Cache Reset for Testing

@smartasync
async def my_method():
    pass

# Reset cache between tests
my_method._smartasync_reset_cache()

Limitations

  • ⚠️ Cannot transition from async to sync: Once in async context, cannot move back to sync (this is correct behavior)
  • ⚠️ Sync overhead: Always rechecks context in sync mode (~2 microseconds per call)

Thread Safety

SmartAsync is safe for all common use cases:

Safe scenarios (covers 99% of real-world usage):

  • Single-threaded applications (CLI tools, scripts)
  • Async event loops (inherently single-threaded)
  • Web servers with request isolation (new instance per request)
  • Thread pools with instance-per-thread pattern

⚠️ Theoretical concern (anti-pattern, not recommended):

  • Sharing a single instance across multiple threads in a thread pool

Why this isn't a real issue: The anti-pattern scenario defeats SmartAsync's purpose. If you're using thread pools with shared instances, you should use async workers instead for better performance and natural concurrency.

Recommendation: Create instances per thread/request, or better yet, use async patterns natively.

Related Projects

SmartAsync is part of the Genro-Libs toolkit:

  • smartswitch - Rule-based function dispatch
  • smpub - CLI/API framework (uses SmartAsync for async handlers)
  • gtext - Text transformation and templates

Contributing

Contributions welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE file for details.

Credits

Author: Giovanni Porcari (Genropy Team) Part of: Genro-Libs developer toolkit

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

smartasync-0.3.0.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

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

smartasync-0.3.0-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

File details

Details for the file smartasync-0.3.0.tar.gz.

File metadata

  • Download URL: smartasync-0.3.0.tar.gz
  • Upload date:
  • Size: 14.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartasync-0.3.0.tar.gz
Algorithm Hash digest
SHA256 eea45ee0eea5db59e69179ac0decf2759180181944e27cac22e7dbfa16b147dd
MD5 a8b7bab64ba9a6b9880d070d886bcad5
BLAKE2b-256 8b8dc44ba7053bbe0184628e87e6104519c42b8ee818f54f3133f08b9c43d94e

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartasync-0.3.0.tar.gz:

Publisher: publish.yml on genropy/smartasync

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

File details

Details for the file smartasync-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: smartasync-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 8.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartasync-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 810404184f9077fb0941b8e95f57ce2b85691bece7b2c6e823d566f6ec60c922
MD5 6c846d64396f15a74d2f6c73984b4d66
BLAKE2b-256 e53b7d1eca33991b776daa4b34e6f634a83890ae3dd3eaa00e6762fb05c15517

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartasync-0.3.0-py3-none-any.whl:

Publisher: publish.yml on genropy/smartasync

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