Skip to main content

No project description provided

Project description

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:

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.

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:

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:

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.

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:

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 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:

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:

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))

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gensyncio-1.0.0.tar.gz (12.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gensyncio-1.0.0-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file gensyncio-1.0.0.tar.gz.

File metadata

  • Download URL: gensyncio-1.0.0.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.10.12 Linux/6.5.0-1025-azure

File hashes

Hashes for gensyncio-1.0.0.tar.gz
Algorithm Hash digest
SHA256 5e3dfe2f01990b6560d54650b42ac0285e806edf168072297a0ce5fa8a529efc
MD5 7d962ccd111cf6203a6a94517b0c9473
BLAKE2b-256 2120d2b4d350e9632116b6e36a3e6d91bb06af5d22be0e09fd33bc2d691e9ab4

See more details on using hashes here.

File details

Details for the file gensyncio-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: gensyncio-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 14.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.10.12 Linux/6.5.0-1025-azure

File hashes

Hashes for gensyncio-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7f9f0e955a2953d650f40318d6ad0745326c7569b068eaa3fd912c1095999e3f
MD5 c9c15a7ecf39e6a5ea1330da88dc853c
BLAKE2b-256 1227138a54859a11971f4beaec1d6083d53a71bc7113f52683a84669d8d8de9a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page