smartasync 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smartasync-0.2.0/LICENSE +21 -0
- smartasync-0.2.0/PKG-INFO +254 -0
- smartasync-0.2.0/README.md +204 -0
- smartasync-0.2.0/pyproject.toml +93 -0
- smartasync-0.2.0/setup.cfg +4 -0
- smartasync-0.2.0/src/smartasync/__init__.py +9 -0
- smartasync-0.2.0/src/smartasync/core.py +129 -0
- smartasync-0.2.0/src/smartasync.egg-info/PKG-INFO +254 -0
- smartasync-0.2.0/src/smartasync.egg-info/SOURCES.txt +11 -0
- smartasync-0.2.0/src/smartasync.egg-info/dependency_links.txt +1 -0
- smartasync-0.2.0/src/smartasync.egg-info/requires.txt +28 -0
- smartasync-0.2.0/src/smartasync.egg-info/top_level.txt +1 -0
- smartasync-0.2.0/tests/test_smartasync.py +390 -0
smartasync-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 GenroPy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartasync
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Unified sync/async API decorator with automatic context detection
|
|
5
|
+
Author-email: Giovanni Porcari <softwell@softwell.it>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/genropy/smartasync
|
|
8
|
+
Project-URL: Documentation, https://smartasync.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/genropy/smartasync
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/genropy/smartasync/issues
|
|
11
|
+
Keywords: async,sync,decorator,asyncio,context-detection,unified-api
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Framework :: AsyncIO
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
31
|
+
Provides-Extra: docs
|
|
32
|
+
Requires-Dist: sphinx>=8.0.0; extra == "docs"
|
|
33
|
+
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
|
|
34
|
+
Requires-Dist: sphinx-autodoc-typehints>=3.0.0; extra == "docs"
|
|
35
|
+
Requires-Dist: myst-parser>=4.0.0; extra == "docs"
|
|
36
|
+
Requires-Dist: sphinxcontrib-mermaid>=1.0.0; extra == "docs"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == "all"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
|
|
40
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "all"
|
|
41
|
+
Requires-Dist: black>=23.0.0; extra == "all"
|
|
42
|
+
Requires-Dist: ruff>=0.1.0; extra == "all"
|
|
43
|
+
Requires-Dist: mypy>=1.0.0; extra == "all"
|
|
44
|
+
Requires-Dist: sphinx>=8.0.0; extra == "all"
|
|
45
|
+
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "all"
|
|
46
|
+
Requires-Dist: sphinx-autodoc-typehints>=3.0.0; extra == "all"
|
|
47
|
+
Requires-Dist: myst-parser>=4.0.0; extra == "all"
|
|
48
|
+
Requires-Dist: sphinxcontrib-mermaid>=1.0.0; extra == "all"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# SmartAsync
|
|
52
|
+
|
|
53
|
+
**Unified sync/async API decorator with automatic context detection**
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/smartasync/)
|
|
56
|
+
[](https://github.com/genropy/smartasync/actions/workflows/test.yml)
|
|
57
|
+
[](https://codecov.io/gh/genropy/smartasync)
|
|
58
|
+
[](https://smartasync.readthedocs.io/en/latest/?badge=latest)
|
|
59
|
+
[](https://www.python.org/downloads/)
|
|
60
|
+
[](https://opensource.org/licenses/MIT)
|
|
61
|
+
[](https://github.com/softwell/genro-libs)
|
|
62
|
+
[](llm-docs/)
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- ✅ **Automatic context detection**: Detects sync vs async execution context at runtime
|
|
69
|
+
- ✅ **Zero configuration**: Just apply the `@smartasync` decorator
|
|
70
|
+
- ✅ **Asymmetric caching**: Smart caching strategy for optimal performance
|
|
71
|
+
- ✅ **Compatible with `__slots__`**: Works with memory-optimized classes
|
|
72
|
+
- ✅ **Pure Python**: No dependencies beyond standard library
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install smartasync
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from smartasync import smartasync
|
|
84
|
+
import asyncio
|
|
85
|
+
|
|
86
|
+
class DataManager:
|
|
87
|
+
@smartasync
|
|
88
|
+
async def fetch_data(self, url: str):
|
|
89
|
+
"""Fetch data - works in both sync and async contexts!"""
|
|
90
|
+
async with httpx.AsyncClient() as client:
|
|
91
|
+
response = await client.get(url)
|
|
92
|
+
return response.json()
|
|
93
|
+
|
|
94
|
+
# Sync context - no await needed
|
|
95
|
+
manager = DataManager()
|
|
96
|
+
data = manager.fetch_data("https://api.example.com/data")
|
|
97
|
+
|
|
98
|
+
# Async context - use await
|
|
99
|
+
async def main():
|
|
100
|
+
manager = DataManager()
|
|
101
|
+
data = await manager.fetch_data("https://api.example.com/data")
|
|
102
|
+
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
SmartAsync uses `asyncio.get_running_loop()` to detect the execution context:
|
|
109
|
+
|
|
110
|
+
- **Sync context** (no event loop): Executes with `asyncio.run()`
|
|
111
|
+
- **Async context** (event loop running): Returns coroutine to be awaited
|
|
112
|
+
|
|
113
|
+
### Asymmetric Caching
|
|
114
|
+
|
|
115
|
+
SmartAsync uses an intelligent caching strategy:
|
|
116
|
+
- ✅ **Async context detected**: Cached forever (can't transition from async to sync)
|
|
117
|
+
- ⚠️ **Sync context**: Always rechecked (can transition from sync to async)
|
|
118
|
+
|
|
119
|
+
This ensures correct behavior while optimizing for the most common case (async contexts in web frameworks).
|
|
120
|
+
|
|
121
|
+
## Use Cases
|
|
122
|
+
|
|
123
|
+
### 1. CLI + HTTP API
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from smartasync import smartasync
|
|
127
|
+
from smpub import PublishedClass, ApiSwitcher
|
|
128
|
+
|
|
129
|
+
class DataHandler(PublishedClass):
|
|
130
|
+
api = ApiSwitcher()
|
|
131
|
+
|
|
132
|
+
@api
|
|
133
|
+
@smartasync
|
|
134
|
+
async def process_data(self, input_file: str):
|
|
135
|
+
"""Process data file."""
|
|
136
|
+
async with aiofiles.open(input_file) as f:
|
|
137
|
+
data = await f.read()
|
|
138
|
+
return process(data)
|
|
139
|
+
|
|
140
|
+
# CLI usage (sync)
|
|
141
|
+
handler = DataHandler()
|
|
142
|
+
result = handler.process_data("data.csv")
|
|
143
|
+
|
|
144
|
+
# HTTP usage (async via FastAPI)
|
|
145
|
+
# Automatically works without modification!
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 2. Testing
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
@smartasync
|
|
152
|
+
async def database_query(query: str):
|
|
153
|
+
async with database.connect() as conn:
|
|
154
|
+
return await conn.execute(query)
|
|
155
|
+
|
|
156
|
+
# Sync tests
|
|
157
|
+
def test_query():
|
|
158
|
+
result = database_query("SELECT * FROM users")
|
|
159
|
+
assert len(result) > 0
|
|
160
|
+
|
|
161
|
+
# Async tests
|
|
162
|
+
async def test_query_async():
|
|
163
|
+
result = await database_query("SELECT * FROM users")
|
|
164
|
+
assert len(result) > 0
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3. Mixed Codebases
|
|
168
|
+
|
|
169
|
+
Perfect for gradually migrating sync code to async without breaking existing callers.
|
|
170
|
+
|
|
171
|
+
## Performance
|
|
172
|
+
|
|
173
|
+
- **Decoration time**: ~3-4 microseconds (one-time cost)
|
|
174
|
+
- **Sync context**: ~102 microseconds (dominated by `asyncio.run()` overhead)
|
|
175
|
+
- **Async context (first call)**: ~2.3 microseconds
|
|
176
|
+
- **Async context (cached)**: ~1.3 microseconds
|
|
177
|
+
|
|
178
|
+
For typical CLI tools and web APIs, this overhead is negligible compared to network latency (10-200ms).
|
|
179
|
+
|
|
180
|
+
## Advanced Usage
|
|
181
|
+
|
|
182
|
+
### With `__slots__`
|
|
183
|
+
|
|
184
|
+
SmartAsync works seamlessly with `__slots__` classes:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from smartasync import smartasync
|
|
188
|
+
|
|
189
|
+
class OptimizedManager:
|
|
190
|
+
__slots__ = ('data',)
|
|
191
|
+
|
|
192
|
+
def __init__(self):
|
|
193
|
+
self.data = []
|
|
194
|
+
|
|
195
|
+
@smartasync
|
|
196
|
+
async def add_item(self, item):
|
|
197
|
+
await asyncio.sleep(0.01) # Simulate I/O
|
|
198
|
+
self.data.append(item)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Cache Reset for Testing
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
@smartasync
|
|
205
|
+
async def my_method():
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# Reset cache between tests
|
|
209
|
+
my_method._smartasync_reset_cache()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Limitations
|
|
213
|
+
|
|
214
|
+
- ⚠️ **Cannot transition from async to sync**: Once in async context, cannot move back to sync (this is correct behavior)
|
|
215
|
+
- ⚠️ **Sync overhead**: Always rechecks context in sync mode (~2 microseconds per call)
|
|
216
|
+
|
|
217
|
+
## Thread Safety
|
|
218
|
+
|
|
219
|
+
SmartAsync is **safe for all common use cases**:
|
|
220
|
+
|
|
221
|
+
✅ **Safe scenarios** (covers 99% of real-world usage):
|
|
222
|
+
- Single-threaded applications (CLI tools, scripts)
|
|
223
|
+
- Async event loops (inherently single-threaded)
|
|
224
|
+
- Web servers with request isolation (new instance per request)
|
|
225
|
+
- Thread pools with instance-per-thread pattern
|
|
226
|
+
|
|
227
|
+
⚠️ **Theoretical concern** (anti-pattern, not recommended):
|
|
228
|
+
- Sharing a single instance across multiple threads in a thread pool
|
|
229
|
+
|
|
230
|
+
**Why this isn't a real issue:**
|
|
231
|
+
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.
|
|
232
|
+
|
|
233
|
+
**Recommendation:** Create instances per thread/request, or better yet, use async patterns natively.
|
|
234
|
+
|
|
235
|
+
## Related Projects
|
|
236
|
+
|
|
237
|
+
SmartAsync is part of the **Genro-Libs toolkit**:
|
|
238
|
+
|
|
239
|
+
- [smartswitch](https://github.com/genropy/smartswitch) - Rule-based function dispatch
|
|
240
|
+
- [smpub](https://github.com/genropy/smpub) - CLI/API framework (uses SmartAsync for async handlers)
|
|
241
|
+
- [gtext](https://github.com/genropy/gtext) - Text transformation and templates
|
|
242
|
+
|
|
243
|
+
## Contributing
|
|
244
|
+
|
|
245
|
+
Contributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
250
|
+
|
|
251
|
+
## Credits
|
|
252
|
+
|
|
253
|
+
**Author**: Giovanni Porcari (Genropy Team)
|
|
254
|
+
**Part of**: [Genro-Libs](https://github.com/softwell/genro-libs) developer toolkit
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# SmartAsync
|
|
2
|
+
|
|
3
|
+
**Unified sync/async API decorator with automatic context detection**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/smartasync/)
|
|
6
|
+
[](https://github.com/genropy/smartasync/actions/workflows/test.yml)
|
|
7
|
+
[](https://codecov.io/gh/genropy/smartasync)
|
|
8
|
+
[](https://smartasync.readthedocs.io/en/latest/?badge=latest)
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://github.com/softwell/genro-libs)
|
|
12
|
+
[](llm-docs/)
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- ✅ **Automatic context detection**: Detects sync vs async execution context at runtime
|
|
19
|
+
- ✅ **Zero configuration**: Just apply the `@smartasync` decorator
|
|
20
|
+
- ✅ **Asymmetric caching**: Smart caching strategy for optimal performance
|
|
21
|
+
- ✅ **Compatible with `__slots__`**: Works with memory-optimized classes
|
|
22
|
+
- ✅ **Pure Python**: No dependencies beyond standard library
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install smartasync
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from smartasync import smartasync
|
|
34
|
+
import asyncio
|
|
35
|
+
|
|
36
|
+
class DataManager:
|
|
37
|
+
@smartasync
|
|
38
|
+
async def fetch_data(self, url: str):
|
|
39
|
+
"""Fetch data - works in both sync and async contexts!"""
|
|
40
|
+
async with httpx.AsyncClient() as client:
|
|
41
|
+
response = await client.get(url)
|
|
42
|
+
return response.json()
|
|
43
|
+
|
|
44
|
+
# Sync context - no await needed
|
|
45
|
+
manager = DataManager()
|
|
46
|
+
data = manager.fetch_data("https://api.example.com/data")
|
|
47
|
+
|
|
48
|
+
# Async context - use await
|
|
49
|
+
async def main():
|
|
50
|
+
manager = DataManager()
|
|
51
|
+
data = await manager.fetch_data("https://api.example.com/data")
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
SmartAsync uses `asyncio.get_running_loop()` to detect the execution context:
|
|
59
|
+
|
|
60
|
+
- **Sync context** (no event loop): Executes with `asyncio.run()`
|
|
61
|
+
- **Async context** (event loop running): Returns coroutine to be awaited
|
|
62
|
+
|
|
63
|
+
### Asymmetric Caching
|
|
64
|
+
|
|
65
|
+
SmartAsync uses an intelligent caching strategy:
|
|
66
|
+
- ✅ **Async context detected**: Cached forever (can't transition from async to sync)
|
|
67
|
+
- ⚠️ **Sync context**: Always rechecked (can transition from sync to async)
|
|
68
|
+
|
|
69
|
+
This ensures correct behavior while optimizing for the most common case (async contexts in web frameworks).
|
|
70
|
+
|
|
71
|
+
## Use Cases
|
|
72
|
+
|
|
73
|
+
### 1. CLI + HTTP API
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from smartasync import smartasync
|
|
77
|
+
from smpub import PublishedClass, ApiSwitcher
|
|
78
|
+
|
|
79
|
+
class DataHandler(PublishedClass):
|
|
80
|
+
api = ApiSwitcher()
|
|
81
|
+
|
|
82
|
+
@api
|
|
83
|
+
@smartasync
|
|
84
|
+
async def process_data(self, input_file: str):
|
|
85
|
+
"""Process data file."""
|
|
86
|
+
async with aiofiles.open(input_file) as f:
|
|
87
|
+
data = await f.read()
|
|
88
|
+
return process(data)
|
|
89
|
+
|
|
90
|
+
# CLI usage (sync)
|
|
91
|
+
handler = DataHandler()
|
|
92
|
+
result = handler.process_data("data.csv")
|
|
93
|
+
|
|
94
|
+
# HTTP usage (async via FastAPI)
|
|
95
|
+
# Automatically works without modification!
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2. Testing
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
@smartasync
|
|
102
|
+
async def database_query(query: str):
|
|
103
|
+
async with database.connect() as conn:
|
|
104
|
+
return await conn.execute(query)
|
|
105
|
+
|
|
106
|
+
# Sync tests
|
|
107
|
+
def test_query():
|
|
108
|
+
result = database_query("SELECT * FROM users")
|
|
109
|
+
assert len(result) > 0
|
|
110
|
+
|
|
111
|
+
# Async tests
|
|
112
|
+
async def test_query_async():
|
|
113
|
+
result = await database_query("SELECT * FROM users")
|
|
114
|
+
assert len(result) > 0
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Mixed Codebases
|
|
118
|
+
|
|
119
|
+
Perfect for gradually migrating sync code to async without breaking existing callers.
|
|
120
|
+
|
|
121
|
+
## Performance
|
|
122
|
+
|
|
123
|
+
- **Decoration time**: ~3-4 microseconds (one-time cost)
|
|
124
|
+
- **Sync context**: ~102 microseconds (dominated by `asyncio.run()` overhead)
|
|
125
|
+
- **Async context (first call)**: ~2.3 microseconds
|
|
126
|
+
- **Async context (cached)**: ~1.3 microseconds
|
|
127
|
+
|
|
128
|
+
For typical CLI tools and web APIs, this overhead is negligible compared to network latency (10-200ms).
|
|
129
|
+
|
|
130
|
+
## Advanced Usage
|
|
131
|
+
|
|
132
|
+
### With `__slots__`
|
|
133
|
+
|
|
134
|
+
SmartAsync works seamlessly with `__slots__` classes:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from smartasync import smartasync
|
|
138
|
+
|
|
139
|
+
class OptimizedManager:
|
|
140
|
+
__slots__ = ('data',)
|
|
141
|
+
|
|
142
|
+
def __init__(self):
|
|
143
|
+
self.data = []
|
|
144
|
+
|
|
145
|
+
@smartasync
|
|
146
|
+
async def add_item(self, item):
|
|
147
|
+
await asyncio.sleep(0.01) # Simulate I/O
|
|
148
|
+
self.data.append(item)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Cache Reset for Testing
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
@smartasync
|
|
155
|
+
async def my_method():
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
# Reset cache between tests
|
|
159
|
+
my_method._smartasync_reset_cache()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Limitations
|
|
163
|
+
|
|
164
|
+
- ⚠️ **Cannot transition from async to sync**: Once in async context, cannot move back to sync (this is correct behavior)
|
|
165
|
+
- ⚠️ **Sync overhead**: Always rechecks context in sync mode (~2 microseconds per call)
|
|
166
|
+
|
|
167
|
+
## Thread Safety
|
|
168
|
+
|
|
169
|
+
SmartAsync is **safe for all common use cases**:
|
|
170
|
+
|
|
171
|
+
✅ **Safe scenarios** (covers 99% of real-world usage):
|
|
172
|
+
- Single-threaded applications (CLI tools, scripts)
|
|
173
|
+
- Async event loops (inherently single-threaded)
|
|
174
|
+
- Web servers with request isolation (new instance per request)
|
|
175
|
+
- Thread pools with instance-per-thread pattern
|
|
176
|
+
|
|
177
|
+
⚠️ **Theoretical concern** (anti-pattern, not recommended):
|
|
178
|
+
- Sharing a single instance across multiple threads in a thread pool
|
|
179
|
+
|
|
180
|
+
**Why this isn't a real issue:**
|
|
181
|
+
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.
|
|
182
|
+
|
|
183
|
+
**Recommendation:** Create instances per thread/request, or better yet, use async patterns natively.
|
|
184
|
+
|
|
185
|
+
## Related Projects
|
|
186
|
+
|
|
187
|
+
SmartAsync is part of the **Genro-Libs toolkit**:
|
|
188
|
+
|
|
189
|
+
- [smartswitch](https://github.com/genropy/smartswitch) - Rule-based function dispatch
|
|
190
|
+
- [smpub](https://github.com/genropy/smpub) - CLI/API framework (uses SmartAsync for async handlers)
|
|
191
|
+
- [gtext](https://github.com/genropy/gtext) - Text transformation and templates
|
|
192
|
+
|
|
193
|
+
## Contributing
|
|
194
|
+
|
|
195
|
+
Contributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
200
|
+
|
|
201
|
+
## Credits
|
|
202
|
+
|
|
203
|
+
**Author**: Giovanni Porcari (Genropy Team)
|
|
204
|
+
**Part of**: [Genro-Libs](https://github.com/softwell/genro-libs) developer toolkit
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smartasync"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Unified sync/async API decorator with automatic context detection"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Giovanni Porcari", email = "softwell@softwell.it"},
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Framework :: AsyncIO",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
keywords = ["async", "sync", "decorator", "asyncio", "context-detection", "unified-api"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=7.0.0",
|
|
31
|
+
"pytest-asyncio>=0.21.0",
|
|
32
|
+
"pytest-cov>=4.0.0",
|
|
33
|
+
"black>=23.0.0",
|
|
34
|
+
"ruff>=0.1.0",
|
|
35
|
+
"mypy>=1.0.0",
|
|
36
|
+
]
|
|
37
|
+
docs = [
|
|
38
|
+
"sphinx>=8.0.0",
|
|
39
|
+
"sphinx-rtd-theme>=3.0.0",
|
|
40
|
+
"sphinx-autodoc-typehints>=3.0.0",
|
|
41
|
+
"myst-parser>=4.0.0",
|
|
42
|
+
"sphinxcontrib-mermaid>=1.0.0",
|
|
43
|
+
]
|
|
44
|
+
all = [
|
|
45
|
+
"pytest>=7.0.0",
|
|
46
|
+
"pytest-asyncio>=0.21.0",
|
|
47
|
+
"pytest-cov>=4.0.0",
|
|
48
|
+
"black>=23.0.0",
|
|
49
|
+
"ruff>=0.1.0",
|
|
50
|
+
"mypy>=1.0.0",
|
|
51
|
+
"sphinx>=8.0.0",
|
|
52
|
+
"sphinx-rtd-theme>=3.0.0",
|
|
53
|
+
"sphinx-autodoc-typehints>=3.0.0",
|
|
54
|
+
"myst-parser>=4.0.0",
|
|
55
|
+
"sphinxcontrib-mermaid>=1.0.0",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[project.urls]
|
|
59
|
+
Homepage = "https://github.com/genropy/smartasync"
|
|
60
|
+
Documentation = "https://smartasync.readthedocs.io"
|
|
61
|
+
Repository = "https://github.com/genropy/smartasync"
|
|
62
|
+
"Bug Tracker" = "https://github.com/genropy/smartasync/issues"
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.packages.find]
|
|
65
|
+
where = ["src"]
|
|
66
|
+
include = ["smartasync*"]
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
testpaths = ["tests"]
|
|
70
|
+
python_files = ["test_*.py"]
|
|
71
|
+
python_classes = ["Test*"]
|
|
72
|
+
python_functions = ["test_*"]
|
|
73
|
+
addopts = "-v --cov=smartasync --cov-report=term-missing --cov-report=html --cov-report=xml"
|
|
74
|
+
asyncio_mode = "auto"
|
|
75
|
+
|
|
76
|
+
[tool.black]
|
|
77
|
+
line-length = 100
|
|
78
|
+
target-version = ['py310', 'py311', 'py312']
|
|
79
|
+
|
|
80
|
+
[tool.ruff]
|
|
81
|
+
line-length = 100
|
|
82
|
+
target-version = "py310"
|
|
83
|
+
src = ["src"]
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint]
|
|
86
|
+
select = ["E", "F", "W", "I", "N"]
|
|
87
|
+
|
|
88
|
+
[tool.mypy]
|
|
89
|
+
python_version = "3.10"
|
|
90
|
+
warn_return_any = true
|
|
91
|
+
warn_unused_configs = true
|
|
92
|
+
disallow_untyped_defs = false
|
|
93
|
+
files = ["src/smartasync"]
|