gensyncio


Namegensyncio JSON
Version 1.0.0 PyPI version JSON
download
home_pageNone
SummaryNone
upload_time2024-12-03 21:04:20
maintainerNone
docs_urlNone
authorPavel Kirilin
requires_python<4.0,>=3.9
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Generator-based async framework

This project was created for educational purpose to demonstrate people how to write their own async
framework with event loop, socket primitives and an HTTP server.


# Lib

The lib is pretty similar with asyncio module. Here's a simple example of an async program:


```python
from typing import Generator
import gensyncio


def main() -> Generator[None, None, None]:
    yield from gensyncio.sleep(1)
    print("Hello from async!")


print(gensyncio.run(main()))
```

You can achieve real async by creating tasks in an event loop and gathering them.

```python
from typing import Generator
import gensyncio


def print_after_delay(msg: str, delay: int):
    yield from gensyncio.sleep(delay)
    print(msg)


def main() -> Generator[None, None, None]:
    yield from gensyncio.gather(
        print_after_delay("Hello", 1),
        print_after_delay("from", 1),
        print_after_delay("async", 1),
    )


gensyncio.run(main())
```

# Sync primitives

This lib implements syncronization primitives for async programming. Such as:

* Event
* Lock

Here are examples of how they should be used:

An event:
```python
import gensyncio


def waiter(event: gensyncio.Event):
    print("waiting for it ...")
    yield from event.wait()
    print("... got it!")


def main():
    # Create an Event object.
    event = gensyncio.Event()

    # Spawn a Task to wait until 'event' is set.
    waiter_task = gensyncio.create_task(waiter(event))

    # Sleep for 1 second and set the event.
    yield from gensyncio.sleep(1)
    event.set()

    # Wait until the waiter task is finished.
    yield from waiter_task


gensyncio.run(main())
```

A lock:

```python
from typing import Generator
import gensyncio


def print_after(lock: gensyncio.Lock, delay: float, val: str) -> Generator[None, None, None]:
    """Print after delay, but wit aquiring a lock."""
    # Here we are using the lock as a context manager
    with lock as _lock:
        # This will yield from the lock, and wait until the lock is released
        yield from _lock
        # This will yield from the sleep, and wait until the sleep is done
        yield from gensyncio.sleep(delay)
    print(val)


def main() -> Generator[None, None, None]:
    loop = gensyncio.get_running_loop()
    lock = gensyncio.Lock()
    loop.create_task(print_after(lock, 2, "one"))
    t = loop.create_task(print_after(lock, 1, "two"))
    # Here we wait for the task to finish
    yield from t


gensyncio.run(main())
```

# Queue

The queue is the same as asyncio Queue. This example is rewritten asyncio.Queue example from python docs.

```python
import random
import time

import gensyncio


def worker(name: str, queue: gensyncio.Queue[float]):
    while True:
        # Get a "work item" out of the queue.
        sleep_for = yield from queue.get()

        # Sleep for the "sleep_for" seconds.
        yield from gensyncio.sleep(sleep_for)

        # Notify the queue that the "work item" has been processed.
        queue.task_done()

        print(f"{name} has slept for {sleep_for:.2f} seconds")


def main():
    # Create a queue that we will use to store our "workload".
    queue = gensyncio.Queue()

    # Generate random timings and put them into the queue.
    total_sleep_time = 0
    for _ in range(20):
        sleep_for = random.uniform(0.05, 1.0)
        total_sleep_time += sleep_for
        queue.put_nowait(sleep_for)

    # Create three worker tasks to process the queue concurrently.
    tasks = []
    for i in range(3):
        task = gensyncio.create_task(worker(f"worker-{i}", queue))
        tasks.append(task)

    # Wait until the queue is fully processed.
    started_at = time.monotonic()
    yield from queue.join()
    total_slept_for = time.monotonic() - started_at

    # Cancel our worker tasks.
    for task in tasks:
        try:
            task.cancel()
        except gensyncio.GenCancelledError:
            pass
    # Wait until all worker tasks are cancelled.
    yield from gensyncio.gather(*tasks)

    print("====")
    print(f"3 workers slept in parallel for {total_slept_for:.2f} seconds")
    print(f"total expected sleep time: {total_sleep_time:.2f} seconds")


gensyncio.run(main())
```

# Sockets

Also this lib contains a simple socket implementation which is compatible with generators approach.
Here's a simple example of using generator based socket:

```python
import socket
from typing import Generator
import gensyncio
from gensyncio.gensocket import GenSocket


def main() -> Generator[None, None, None]:
    sock = GenSocket(socket.AF_INET, socket.SOCK_STREAM)
    yield from sock.connect(("httpbin.org", 80))
    sock.send(b"GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n")
    resp = yield from sock.recv(1024)
    print(resp.decode("utf-8"))


gensyncio.run(main())
```

GenSocket is alsmost similar to [socket.socket](https://docs.python.org/3/library/socket.html) except it's always nonblocking and some of it's methods should be awaited using `yield from`.


# Http module

Also there's a small HTTP module which you can use to serve and send requests. 

Here's a client usage example:

```python
import gensyncio
from gensyncio.http import ClientRequest


def main():
    yield
    req = ClientRequest("http://localhost:8080/", "GET", json={"one": "two"})
    resp = yield from req.send()
    print(resp)
    print(resp.body.decode("utf-8"))


gensyncio.run(main())
```

And here's a simple echo server example:

```python
import logging
from typing import Generator
from gensyncio.http import Server
import gensyncio
from gensyncio.http.server import Request, Response

app = Server()


@app.router.get("/")
def index(req: Request) -> Generator[None, None, Response]:
    body = yield from req.read()
    return Response(status=200, body=body, content_type=req.content_type)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    gensyncio.run(app.run(host="0.0.0.0", port=8080))
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "gensyncio",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": "Pavel Kirilin",
    "author_email": "win10@list.ru",
    "download_url": "https://files.pythonhosted.org/packages/21/20/d2b4d350e9632116b6e36a3e6d91bb06af5d22be0e09fd33bc2d691e9ab4/gensyncio-1.0.0.tar.gz",
    "platform": null,
    "description": "# Generator-based async framework\n\nThis project was created for educational purpose to demonstrate people how to write their own async\nframework with event loop, socket primitives and an HTTP server.\n\n\n# Lib\n\nThe lib is pretty similar with asyncio module. Here's a simple example of an async program:\n\n\n```python\nfrom typing import Generator\nimport gensyncio\n\n\ndef main() -> Generator[None, None, None]:\n    yield from gensyncio.sleep(1)\n    print(\"Hello from async!\")\n\n\nprint(gensyncio.run(main()))\n```\n\nYou can achieve real async by creating tasks in an event loop and gathering them.\n\n```python\nfrom typing import Generator\nimport gensyncio\n\n\ndef print_after_delay(msg: str, delay: int):\n    yield from gensyncio.sleep(delay)\n    print(msg)\n\n\ndef main() -> Generator[None, None, None]:\n    yield from gensyncio.gather(\n        print_after_delay(\"Hello\", 1),\n        print_after_delay(\"from\", 1),\n        print_after_delay(\"async\", 1),\n    )\n\n\ngensyncio.run(main())\n```\n\n# Sync primitives\n\nThis lib implements syncronization primitives for async programming. Such as:\n\n* Event\n* Lock\n\nHere are examples of how they should be used:\n\nAn event:\n```python\nimport gensyncio\n\n\ndef waiter(event: gensyncio.Event):\n    print(\"waiting for it ...\")\n    yield from event.wait()\n    print(\"... got it!\")\n\n\ndef main():\n    # Create an Event object.\n    event = gensyncio.Event()\n\n    # Spawn a Task to wait until 'event' is set.\n    waiter_task = gensyncio.create_task(waiter(event))\n\n    # Sleep for 1 second and set the event.\n    yield from gensyncio.sleep(1)\n    event.set()\n\n    # Wait until the waiter task is finished.\n    yield from waiter_task\n\n\ngensyncio.run(main())\n```\n\nA lock:\n\n```python\nfrom typing import Generator\nimport gensyncio\n\n\ndef print_after(lock: gensyncio.Lock, delay: float, val: str) -> Generator[None, None, None]:\n    \"\"\"Print after delay, but wit aquiring a lock.\"\"\"\n    # Here we are using the lock as a context manager\n    with lock as _lock:\n        # This will yield from the lock, and wait until the lock is released\n        yield from _lock\n        # This will yield from the sleep, and wait until the sleep is done\n        yield from gensyncio.sleep(delay)\n    print(val)\n\n\ndef main() -> Generator[None, None, None]:\n    loop = gensyncio.get_running_loop()\n    lock = gensyncio.Lock()\n    loop.create_task(print_after(lock, 2, \"one\"))\n    t = loop.create_task(print_after(lock, 1, \"two\"))\n    # Here we wait for the task to finish\n    yield from t\n\n\ngensyncio.run(main())\n```\n\n# Queue\n\nThe queue is the same as asyncio Queue. This example is rewritten asyncio.Queue example from python docs.\n\n```python\nimport random\nimport time\n\nimport gensyncio\n\n\ndef worker(name: str, queue: gensyncio.Queue[float]):\n    while True:\n        # Get a \"work item\" out of the queue.\n        sleep_for = yield from queue.get()\n\n        # Sleep for the \"sleep_for\" seconds.\n        yield from gensyncio.sleep(sleep_for)\n\n        # Notify the queue that the \"work item\" has been processed.\n        queue.task_done()\n\n        print(f\"{name} has slept for {sleep_for:.2f} seconds\")\n\n\ndef main():\n    # Create a queue that we will use to store our \"workload\".\n    queue = gensyncio.Queue()\n\n    # Generate random timings and put them into the queue.\n    total_sleep_time = 0\n    for _ in range(20):\n        sleep_for = random.uniform(0.05, 1.0)\n        total_sleep_time += sleep_for\n        queue.put_nowait(sleep_for)\n\n    # Create three worker tasks to process the queue concurrently.\n    tasks = []\n    for i in range(3):\n        task = gensyncio.create_task(worker(f\"worker-{i}\", queue))\n        tasks.append(task)\n\n    # Wait until the queue is fully processed.\n    started_at = time.monotonic()\n    yield from queue.join()\n    total_slept_for = time.monotonic() - started_at\n\n    # Cancel our worker tasks.\n    for task in tasks:\n        try:\n            task.cancel()\n        except gensyncio.GenCancelledError:\n            pass\n    # Wait until all worker tasks are cancelled.\n    yield from gensyncio.gather(*tasks)\n\n    print(\"====\")\n    print(f\"3 workers slept in parallel for {total_slept_for:.2f} seconds\")\n    print(f\"total expected sleep time: {total_sleep_time:.2f} seconds\")\n\n\ngensyncio.run(main())\n```\n\n# Sockets\n\nAlso this lib contains a simple socket implementation which is compatible with generators approach.\nHere's a simple example of using generator based socket:\n\n```python\nimport socket\nfrom typing import Generator\nimport gensyncio\nfrom gensyncio.gensocket import GenSocket\n\n\ndef main() -> Generator[None, None, None]:\n    sock = GenSocket(socket.AF_INET, socket.SOCK_STREAM)\n    yield from sock.connect((\"httpbin.org\", 80))\n    sock.send(b\"GET /get HTTP/1.1\\r\\nHost: httpbin.org\\r\\n\\r\\n\")\n    resp = yield from sock.recv(1024)\n    print(resp.decode(\"utf-8\"))\n\n\ngensyncio.run(main())\n```\n\nGenSocket is alsmost similar to [socket.socket](https://docs.python.org/3/library/socket.html) except it's always nonblocking and some of it's methods should be awaited using `yield from`.\n\n\n# Http module\n\nAlso there's a small HTTP module which you can use to serve and send requests. \n\nHere's a client usage example:\n\n```python\nimport gensyncio\nfrom gensyncio.http import ClientRequest\n\n\ndef main():\n    yield\n    req = ClientRequest(\"http://localhost:8080/\", \"GET\", json={\"one\": \"two\"})\n    resp = yield from req.send()\n    print(resp)\n    print(resp.body.decode(\"utf-8\"))\n\n\ngensyncio.run(main())\n```\n\nAnd here's a simple echo server example:\n\n```python\nimport logging\nfrom typing import Generator\nfrom gensyncio.http import Server\nimport gensyncio\nfrom gensyncio.http.server import Request, Response\n\napp = Server()\n\n\n@app.router.get(\"/\")\ndef index(req: Request) -> Generator[None, None, Response]:\n    body = yield from req.read()\n    return Response(status=200, body=body, content_type=req.content_type)\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(level=logging.INFO)\n    gensyncio.run(app.run(host=\"0.0.0.0\", port=8080))\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": null,
    "version": "1.0.0",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1227138a54859a11971f4beaec1d6083d53a71bc7113f52683a84669d8d8de9a",
                "md5": "c9c15a7ecf39e6a5ea1330da88dc853c",
                "sha256": "7f9f0e955a2953d650f40318d6ad0745326c7569b068eaa3fd912c1095999e3f"
            },
            "downloads": -1,
            "filename": "gensyncio-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c9c15a7ecf39e6a5ea1330da88dc853c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 14320,
            "upload_time": "2024-12-03T21:04:19",
            "upload_time_iso_8601": "2024-12-03T21:04:19.086473Z",
            "url": "https://files.pythonhosted.org/packages/12/27/138a54859a11971f4beaec1d6083d53a71bc7113f52683a84669d8d8de9a/gensyncio-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2120d2b4d350e9632116b6e36a3e6d91bb06af5d22be0e09fd33bc2d691e9ab4",
                "md5": "7d962ccd111cf6203a6a94517b0c9473",
                "sha256": "5e3dfe2f01990b6560d54650b42ac0285e806edf168072297a0ce5fa8a529efc"
            },
            "downloads": -1,
            "filename": "gensyncio-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7d962ccd111cf6203a6a94517b0c9473",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 11958,
            "upload_time": "2024-12-03T21:04:20",
            "upload_time_iso_8601": "2024-12-03T21:04:20.369506Z",
            "url": "https://files.pythonhosted.org/packages/21/20/d2b4d350e9632116b6e36a3e6d91bb06af5d22be0e09fd33bc2d691e9ab4/gensyncio-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-03 21:04:20",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "gensyncio"
}
        
Elapsed time: 0.42596s