smartasync


Namesmartasync JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummaryUnified sync/async API decorator with automatic context detection
upload_time2025-11-10 23:26:40
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT
keywords async sync decorator asyncio context-detection unified-api
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # SmartAsync

**Unified sync/async API decorator with automatic context detection**

[![PyPI version](https://img.shields.io/pypi/v/smartasync.svg)](https://pypi.org/project/smartasync/)
[![Tests](https://github.com/genropy/smartasync/actions/workflows/test.yml/badge.svg)](https://github.com/genropy/smartasync/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/genropy/smartasync/branch/main/graph/badge.svg)](https://codecov.io/gh/genropy/smartasync)
[![Documentation Status](https://readthedocs.org/projects/smartasync/badge/?version=latest)](https://smartasync.readthedocs.io/en/latest/?badge=latest)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Part of Genro-Libs](https://img.shields.io/badge/Part%20of-Genro--Libs-blue)](https://github.com/softwell/genro-libs)
[![LLM Docs](https://img.shields.io/badge/LLM-Docs-purple)](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

```bash
pip install smartasync
```

## Quick Start

```python
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

```python
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

```python
@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:

```python
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

```python
@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](https://github.com/genropy/smartswitch) - Rule-based function dispatch
- [smpub](https://github.com/genropy/smpub) - CLI/API framework (uses SmartAsync for async handlers)
- [gtext](https://github.com/genropy/gtext) - Text transformation and templates

## Contributing

Contributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Credits

**Author**: Giovanni Porcari (Genropy Team)
**Part of**: [Genro-Libs](https://github.com/softwell/genro-libs) developer toolkit

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "smartasync",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "async, sync, decorator, asyncio, context-detection, unified-api",
    "author": null,
    "author_email": "Giovanni Porcari <softwell@softwell.it>",
    "download_url": "https://files.pythonhosted.org/packages/31/4a/887e8795de20a68d47861b95eff964ac62be42f946ae58ae25db1ce86156/smartasync-0.2.0.tar.gz",
    "platform": null,
    "description": "# SmartAsync\n\n**Unified sync/async API decorator with automatic context detection**\n\n[![PyPI version](https://img.shields.io/pypi/v/smartasync.svg)](https://pypi.org/project/smartasync/)\n[![Tests](https://github.com/genropy/smartasync/actions/workflows/test.yml/badge.svg)](https://github.com/genropy/smartasync/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/genropy/smartasync/branch/main/graph/badge.svg)](https://codecov.io/gh/genropy/smartasync)\n[![Documentation Status](https://readthedocs.org/projects/smartasync/badge/?version=latest)](https://smartasync.readthedocs.io/en/latest/?badge=latest)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Part of Genro-Libs](https://img.shields.io/badge/Part%20of-Genro--Libs-blue)](https://github.com/softwell/genro-libs)\n[![LLM Docs](https://img.shields.io/badge/LLM-Docs-purple)](llm-docs/)\n\nSmartAsync 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.\n\n## Features\n\n- \u2705 **Automatic context detection**: Detects sync vs async execution context at runtime\n- \u2705 **Zero configuration**: Just apply the `@smartasync` decorator\n- \u2705 **Asymmetric caching**: Smart caching strategy for optimal performance\n- \u2705 **Compatible with `__slots__`**: Works with memory-optimized classes\n- \u2705 **Pure Python**: No dependencies beyond standard library\n\n## Installation\n\n```bash\npip install smartasync\n```\n\n## Quick Start\n\n```python\nfrom smartasync import smartasync\nimport asyncio\n\nclass DataManager:\n    @smartasync\n    async def fetch_data(self, url: str):\n        \"\"\"Fetch data - works in both sync and async contexts!\"\"\"\n        async with httpx.AsyncClient() as client:\n            response = await client.get(url)\n            return response.json()\n\n# Sync context - no await needed\nmanager = DataManager()\ndata = manager.fetch_data(\"https://api.example.com/data\")\n\n# Async context - use await\nasync def main():\n    manager = DataManager()\n    data = await manager.fetch_data(\"https://api.example.com/data\")\n\nasyncio.run(main())\n```\n\n## How It Works\n\nSmartAsync uses `asyncio.get_running_loop()` to detect the execution context:\n\n- **Sync context** (no event loop): Executes with `asyncio.run()`\n- **Async context** (event loop running): Returns coroutine to be awaited\n\n### Asymmetric Caching\n\nSmartAsync uses an intelligent caching strategy:\n- \u2705 **Async context detected**: Cached forever (can't transition from async to sync)\n- \u26a0\ufe0f **Sync context**: Always rechecked (can transition from sync to async)\n\nThis ensures correct behavior while optimizing for the most common case (async contexts in web frameworks).\n\n## Use Cases\n\n### 1. CLI + HTTP API\n\n```python\nfrom smartasync import smartasync\nfrom smpub import PublishedClass, ApiSwitcher\n\nclass DataHandler(PublishedClass):\n    api = ApiSwitcher()\n\n    @api\n    @smartasync\n    async def process_data(self, input_file: str):\n        \"\"\"Process data file.\"\"\"\n        async with aiofiles.open(input_file) as f:\n            data = await f.read()\n        return process(data)\n\n# CLI usage (sync)\nhandler = DataHandler()\nresult = handler.process_data(\"data.csv\")\n\n# HTTP usage (async via FastAPI)\n# Automatically works without modification!\n```\n\n### 2. Testing\n\n```python\n@smartasync\nasync def database_query(query: str):\n    async with database.connect() as conn:\n        return await conn.execute(query)\n\n# Sync tests\ndef test_query():\n    result = database_query(\"SELECT * FROM users\")\n    assert len(result) > 0\n\n# Async tests\nasync def test_query_async():\n    result = await database_query(\"SELECT * FROM users\")\n    assert len(result) > 0\n```\n\n### 3. Mixed Codebases\n\nPerfect for gradually migrating sync code to async without breaking existing callers.\n\n## Performance\n\n- **Decoration time**: ~3-4 microseconds (one-time cost)\n- **Sync context**: ~102 microseconds (dominated by `asyncio.run()` overhead)\n- **Async context (first call)**: ~2.3 microseconds\n- **Async context (cached)**: ~1.3 microseconds\n\nFor typical CLI tools and web APIs, this overhead is negligible compared to network latency (10-200ms).\n\n## Advanced Usage\n\n### With `__slots__`\n\nSmartAsync works seamlessly with `__slots__` classes:\n\n```python\nfrom smartasync import smartasync\n\nclass OptimizedManager:\n    __slots__ = ('data',)\n\n    def __init__(self):\n        self.data = []\n\n    @smartasync\n    async def add_item(self, item):\n        await asyncio.sleep(0.01)  # Simulate I/O\n        self.data.append(item)\n```\n\n### Cache Reset for Testing\n\n```python\n@smartasync\nasync def my_method():\n    pass\n\n# Reset cache between tests\nmy_method._smartasync_reset_cache()\n```\n\n## Limitations\n\n- \u26a0\ufe0f **Cannot transition from async to sync**: Once in async context, cannot move back to sync (this is correct behavior)\n- \u26a0\ufe0f **Sync overhead**: Always rechecks context in sync mode (~2 microseconds per call)\n\n## Thread Safety\n\nSmartAsync is **safe for all common use cases**:\n\n\u2705 **Safe scenarios** (covers 99% of real-world usage):\n- Single-threaded applications (CLI tools, scripts)\n- Async event loops (inherently single-threaded)\n- Web servers with request isolation (new instance per request)\n- Thread pools with instance-per-thread pattern\n\n\u26a0\ufe0f **Theoretical concern** (anti-pattern, not recommended):\n- Sharing a single instance across multiple threads in a thread pool\n\n**Why this isn't a real issue:**\nThe 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.\n\n**Recommendation:** Create instances per thread/request, or better yet, use async patterns natively.\n\n## Related Projects\n\nSmartAsync is part of the **Genro-Libs toolkit**:\n\n- [smartswitch](https://github.com/genropy/smartswitch) - Rule-based function dispatch\n- [smpub](https://github.com/genropy/smpub) - CLI/API framework (uses SmartAsync for async handlers)\n- [gtext](https://github.com/genropy/gtext) - Text transformation and templates\n\n## Contributing\n\nContributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## Credits\n\n**Author**: Giovanni Porcari (Genropy Team)\n**Part of**: [Genro-Libs](https://github.com/softwell/genro-libs) developer toolkit\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Unified sync/async API decorator with automatic context detection",
    "version": "0.2.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/genropy/smartasync/issues",
        "Documentation": "https://smartasync.readthedocs.io",
        "Homepage": "https://github.com/genropy/smartasync",
        "Repository": "https://github.com/genropy/smartasync"
    },
    "split_keywords": [
        "async",
        " sync",
        " decorator",
        " asyncio",
        " context-detection",
        " unified-api"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c592042430c5475704f09267ebe2b4c3fedd89f2644be416dcb83442710c2459",
                "md5": "726f354c5e9e8de3f70954ae50bec2f4",
                "sha256": "13accdf27a27a18aa5fbe96d93ed0a5b98d9184f4b350e08ef6479d642576df4"
            },
            "downloads": -1,
            "filename": "smartasync-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "726f354c5e9e8de3f70954ae50bec2f4",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 7057,
            "upload_time": "2025-11-10T23:26:39",
            "upload_time_iso_8601": "2025-11-10T23:26:39.421334Z",
            "url": "https://files.pythonhosted.org/packages/c5/92/042430c5475704f09267ebe2b4c3fedd89f2644be416dcb83442710c2459/smartasync-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "314a887e8795de20a68d47861b95eff964ac62be42f946ae58ae25db1ce86156",
                "md5": "36b433485812acf51d51cb25b84c9cf8",
                "sha256": "2fc701db97613320c13e9ade6b199c39e9ba56d89fe8875d5d5f274f7ba56818"
            },
            "downloads": -1,
            "filename": "smartasync-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "36b433485812acf51d51cb25b84c9cf8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 9833,
            "upload_time": "2025-11-10T23:26:40",
            "upload_time_iso_8601": "2025-11-10T23:26:40.633133Z",
            "url": "https://files.pythonhosted.org/packages/31/4a/887e8795de20a68d47861b95eff964ac62be42f946ae58ae25db1ce86156/smartasync-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-10 23:26:40",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "genropy",
    "github_project": "smartasync",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "smartasync"
}
        
Elapsed time: 1.91027s