Name | gensyncio JSON |
Version |
1.0.0
JSON |
| download |
home_page | None |
Summary | None |
upload_time | 2024-12-03 21:04:20 |
maintainer | None |
docs_url | None |
author | Pavel Kirilin |
requires_python | <4.0,>=3.9 |
license | None |
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"
}