# unsync
Unsynchronize `asyncio` by using an ambient event loop, or executing in separate threads or processes.
# Quick Overview
Functions marked with the `@unsync` decorator will behave in one of the following ways:
* `async` functions will run in the `unsync.loop` event loop executed from `unsync.thread`
* Regular functions will execute in `unsync.thread_executor`, a `ThreadPoolExecutor`
* Useful for IO bounded work that does not support `asyncio`
* Regular functions marked with `@unsync(cpu_bound=True)` will execute in `unsync.process_executor`, a `ProcessPoolExecutor`
* Useful for CPU bounded work
All `@unsync` functions will return an `Unfuture` object.
This new future type combines the behavior of `asyncio.Future` and `concurrent.Future` with the following changes:
* `Unfuture.set_result` is threadsafe unlike `asyncio.Future`
* `Unfuture` instances can be awaited, even if made from `concurrent.Future`
* `Unfuture.result()` is a blocking operation *except* in `unsync.loop`/`unsync.thread` where
it behaves like `asyncio.Future.result` and will throw an exception if the future is not done
# Examples
## Simple Sleep
A simple sleeping example with `asyncio`:
```python
async def sync_async():
await asyncio.sleep(1)
return 'I hate event loops'
async def main():
future1 = asyncio.create_task(sync_async())
future2 = asyncio.create_task(sync_async())
await future1, future2
print(future1.result() + future2.result())
asyncio.run(main())
# Takes 1 second to run
```
Same example with `unsync`:
```python
@unsync
async def unsync_async():
await asyncio.sleep(1)
return 'I like decorators'
unfuture1 = unsync_async()
unfuture2 = unsync_async()
print(unfuture1.result() + unfuture2.result())
# Takes 1 second to run
```
## Multi-threading an IO-bound function
Synchronous functions can be made to run asynchronously by executing them in a `concurrent.ThreadPoolExecutor`.
This can be easily accomplished by marking the regular function `@unsync`.
```python
@unsync
def non_async_function(seconds):
time.sleep(seconds)
return 'Run concurrently!'
start = time.time()
tasks = [non_async_function(0.1) for _ in range(10)]
print([task.result() for task in tasks])
print('Executed in {} seconds'.format(time.time() - start))
```
Which prints:
['Run concurrently!', 'Run concurrently!', ...]
Executed in 0.10807514190673828 seconds
## Continuations
Using `Unfuture.then` chains asynchronous calls and returns an `Unfuture` that wraps both the source, and continuation.
The continuation is invoked with the source Unfuture as the first argument.
Continuations can be regular functions (which will execute synchronously), or `@unsync` functions.
```python
@unsync
async def initiate(request):
await asyncio.sleep(0.1)
return request + 1
@unsync
async def process(task):
await asyncio.sleep(0.1)
return task.result() * 2
start = time.time()
print(initiate(3).then(process).result())
print('Executed in {} seconds'.format(time.time() - start))
```
Which prints:
8
Executed in 0.20314741134643555 seconds
## Mixing methods
We'll start by converting a regular synchronous function into a threaded `Unfuture` which will begin our request.
```python
@unsync
def non_async_function(num):
time.sleep(0.1)
return num, num + 1
```
We may want to refine the result in another function, so we define the following continuation.
```python
@unsync
async def result_continuation(task):
await asyncio.sleep(0.1)
num, res = task.result()
return num, res * 2
```
We then aggregate all the results into a single dictionary in an async function.
```python
@unsync
async def result_processor(tasks):
output = {}
for task in tasks:
num, res = await task
output[num] = res
return output
```
Executing the full chain of `non_async_function`→`result_continuation`→`result_processor` would look like:
```python
start = time.time()
print(result_processor([non_async_function(i).then(result_continuation) for i in range(10)]).result())
print('Executed in {} seconds'.format(time.time() - start))
```
Which prints:
{0: 2, 1: 4, 2: 6, 3: 8, 4: 10, 5: 12, 6: 14, 7: 16, 8: 18, 9: 20}
Executed in 0.22115683555603027 seconds
## Preserving typing
As far as we know it is not possible to change the return type of a method or function using a decorator.
Therefore, we need a workaround to properly use IntelliSense. You have three options in general:
1. Ignore type warnings.
2. Use a suppression statement where you reach the type warning.
A. When defining the unsynced method by changing the return type to an `Unfuture`.
B. When using the unsynced method.
3. Wrap the function without a decorator. Example:
```python
def function_name(x: str) -> Unfuture[str]:
async_method = unsync(__function_name_synced)
return async_method(x)
def __function_name_synced(x: str) -> str:
return x + 'a'
future_result = function_name('b')
self.assertEqual('ba', future_result.result())
```
## Custom Event Loops
In order to use custom event loops, be sure to set the event loop policy before calling any `@unsync` methods.
For example, to use `uvloop` simply:
```python
import unsync
import uvloop
@unsync
async def main():
# Main entry-point.
...
uvloop.install() # Equivalent to asyncio.set_event_loop_policy(EventLoopPolicy())
main()
```
Raw data
{
"_id": null,
"home_page": "https://github.com/alex-sherman/unsync",
"name": "unsync",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Alex-Sherman",
"author_email": "asherman1024@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/ea/6b/8d9699d37fe8c43c76fda4a7f93ae6aef129576f75c3cacfc9a78a7cc984/unsync-1.4.0.tar.gz",
"platform": "",
"description": "# unsync\nUnsynchronize `asyncio` by using an ambient event loop, or executing in separate threads or processes.\n\n# Quick Overview\n\nFunctions marked with the `@unsync` decorator will behave in one of the following ways:\n* `async` functions will run in the `unsync.loop` event loop executed from `unsync.thread`\n* Regular functions will execute in `unsync.thread_executor`, a `ThreadPoolExecutor`\n * Useful for IO bounded work that does not support `asyncio`\n* Regular functions marked with `@unsync(cpu_bound=True)` will execute in `unsync.process_executor`, a `ProcessPoolExecutor`\n * Useful for CPU bounded work\n\nAll `@unsync` functions will return an `Unfuture` object.\nThis new future type combines the behavior of `asyncio.Future` and `concurrent.Future` with the following changes:\n* `Unfuture.set_result` is threadsafe unlike `asyncio.Future`\n* `Unfuture` instances can be awaited, even if made from `concurrent.Future`\n* `Unfuture.result()` is a blocking operation *except* in `unsync.loop`/`unsync.thread` where\n it behaves like `asyncio.Future.result` and will throw an exception if the future is not done\n\n# Examples\n## Simple Sleep\nA simple sleeping example with `asyncio`:\n```python\nasync def sync_async():\n await asyncio.sleep(1)\n return 'I hate event loops'\n\n\nasync def main():\n future1 = asyncio.create_task(sync_async())\n future2 = asyncio.create_task(sync_async())\n\n await future1, future2\n\n print(future1.result() + future2.result())\n\nasyncio.run(main())\n# Takes 1 second to run\n```\n\nSame example with `unsync`:\n```python\n@unsync\nasync def unsync_async():\n await asyncio.sleep(1)\n return 'I like decorators'\n\nunfuture1 = unsync_async()\nunfuture2 = unsync_async()\nprint(unfuture1.result() + unfuture2.result())\n# Takes 1 second to run\n```\n\n## Multi-threading an IO-bound function\nSynchronous functions can be made to run asynchronously by executing them in a `concurrent.ThreadPoolExecutor`.\nThis can be easily accomplished by marking the regular function `@unsync`.\n```python\n@unsync\ndef non_async_function(seconds):\n time.sleep(seconds)\n return 'Run concurrently!'\n\nstart = time.time()\ntasks = [non_async_function(0.1) for _ in range(10)]\nprint([task.result() for task in tasks])\nprint('Executed in {} seconds'.format(time.time() - start))\n```\nWhich prints:\n\n ['Run concurrently!', 'Run concurrently!', ...]\n Executed in 0.10807514190673828 seconds\n\n## Continuations\nUsing `Unfuture.then` chains asynchronous calls and returns an `Unfuture` that wraps both the source, and continuation.\nThe continuation is invoked with the source Unfuture as the first argument.\nContinuations can be regular functions (which will execute synchronously), or `@unsync` functions.\n```python\n@unsync\nasync def initiate(request):\n await asyncio.sleep(0.1)\n return request + 1\n\n@unsync\nasync def process(task):\n await asyncio.sleep(0.1)\n return task.result() * 2\n\nstart = time.time()\nprint(initiate(3).then(process).result())\nprint('Executed in {} seconds'.format(time.time() - start))\n```\nWhich prints:\n\n 8\n Executed in 0.20314741134643555 seconds\n\n## Mixing methods\n\nWe'll start by converting a regular synchronous function into a threaded `Unfuture` which will begin our request.\n```python\n@unsync\ndef non_async_function(num):\n time.sleep(0.1)\n return num, num + 1\n```\nWe may want to refine the result in another function, so we define the following continuation.\n```python\n@unsync\nasync def result_continuation(task):\n await asyncio.sleep(0.1)\n num, res = task.result()\n return num, res * 2\n```\nWe then aggregate all the results into a single dictionary in an async function.\n```python\n@unsync\nasync def result_processor(tasks):\n output = {}\n for task in tasks:\n num, res = await task\n output[num] = res\n return output\n```\nExecuting the full chain of `non_async_function`→`result_continuation`→`result_processor` would look like:\n```python\nstart = time.time()\nprint(result_processor([non_async_function(i).then(result_continuation) for i in range(10)]).result())\nprint('Executed in {} seconds'.format(time.time() - start))\n```\n\nWhich prints:\n\n {0: 2, 1: 4, 2: 6, 3: 8, 4: 10, 5: 12, 6: 14, 7: 16, 8: 18, 9: 20}\n Executed in 0.22115683555603027 seconds\n\n## Preserving typing\nAs far as we know it is not possible to change the return type of a method or function using a decorator.\nTherefore, we need a workaround to properly use IntelliSense. You have three options in general:\n\n1. Ignore type warnings.\n2. Use a suppression statement where you reach the type warning.\n\n A. When defining the unsynced method by changing the return type to an `Unfuture`.\n \n B. When using the unsynced method.\n \n3. Wrap the function without a decorator. Example:\n ```python \n def function_name(x: str) -> Unfuture[str]:\n async_method = unsync(__function_name_synced)\n return async_method(x)\n\n def __function_name_synced(x: str) -> str:\n return x + 'a'\n\n future_result = function_name('b')\n self.assertEqual('ba', future_result.result())\n ```\n\n## Custom Event Loops\nIn order to use custom event loops, be sure to set the event loop policy before calling any `@unsync` methods.\nFor example, to use `uvloop` simply:\n\n```python\nimport unsync\nimport uvloop\n\n@unsync\nasync def main():\n # Main entry-point.\n ...\n\nuvloop.install() # Equivalent to asyncio.set_event_loop_policy(EventLoopPolicy())\nmain()\n```",
"bugtrack_url": null,
"license": "MIT",
"summary": "Unsynchronize asyncio",
"version": "1.4.0",
"project_urls": {
"Homepage": "https://github.com/alex-sherman/unsync"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ea6b8d9699d37fe8c43c76fda4a7f93ae6aef129576f75c3cacfc9a78a7cc984",
"md5": "6b2119ba4a7e2795dbb544e0cd00705b",
"sha256": "a29e0f8952ffb0b3a0453ce436819a5a1ba2febbb5caa707c319f6f98d35f3c5"
},
"downloads": -1,
"filename": "unsync-1.4.0.tar.gz",
"has_sig": false,
"md5_digest": "6b2119ba4a7e2795dbb544e0cd00705b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 8796,
"upload_time": "2021-10-21T00:46:56",
"upload_time_iso_8601": "2021-10-21T00:46:56.488320Z",
"url": "https://files.pythonhosted.org/packages/ea/6b/8d9699d37fe8c43c76fda4a7f93ae6aef129576f75c3cacfc9a78a7cc984/unsync-1.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2021-10-21 00:46:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "alex-sherman",
"github_project": "unsync",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "unsync"
}