unsync


Nameunsync JSON
Version 1.4.0 PyPI version JSON
download
home_pagehttps://github.com/alex-sherman/unsync
SummaryUnsynchronize asyncio
upload_time2021-10-21 00:46:56
maintainer
docs_urlNone
authorAlex-Sherman
requires_python
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.34245s