{ "info": { "author": "Adrian Sampson", "author_email": "adrian@radbox.org", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Internet", "Topic :: System :: Networking" ], "description": "Bluelet\n=======\n\nBluelet is a simple, pure-Python solution for writing intelligible asynchronous socket applications. It uses `PEP 342 coroutines`_ to make concurrent I/O look and act like sequential programming.\n\nIn this way, it is similar to the `Greenlet`_ green-threads library and its associated packages `Eventlet`_ and `Gevent`_. Bluelet has a simpler, 100% Python implementation that comes at the cost of flexibility and performance when compared to Greenlet-based solutions. However, it should be sufficient for many applications that don't need serious scalability; it can be thought of as a less-horrible alternative to `asyncore`_ or an asynchronous replacement for `SocketServer`_ (and more).\n\n.. _PEP 342 coroutines: http://www.python.org/dev/peps/pep-0342/\n.. _asyncore: http://docs.python.org/library/asyncore.html\n.. _SocketServer: http://docs.python.org/library/socketserver.html\n.. _Greenlet: http://pypi.python.org/pypi/greenlet\n.. _Eventlet: http://eventlet.net/\n.. _Gevent: http://www.gevent.org/\n\nThe \"Echo\" Server\n-----------------\n\nAn \"echo\" server is a canonical stupid example for demonstrating socket programming. It simply accepts connections, reads lines, and writes everything it reads back to the client.\n\nHere's an example using plain Python sockets::\n\n import socket\n listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n listener.bind(('', 4915))\n listener.listen(1)\n while True:\n sock, addr = listener.accept()\n while True:\n data = sock.recv(1024)\n if not data:\n break\n sock.sendall(data)\n\nThe code is very simple, but its synchronousness has a major problem: the server can accept only one connection at a time. This won't do even for very small server applications.\n\nOne solution to this problem is to fork several operating system threads or processes that each run the same synchronous code. This, however, quickly becomes complex and makes the application harder to manage. Python's asyncore module provides a way to write *asynchronous* servers that accept multiple connections in the same OS thread::\n\n import asyncore\n import socket\n class Echoer(asyncore.dispatcher_with_send):\n def handle_read(self):\n data = self.recv(1024)\n self.send(data)\n class EchoServer(asyncore.dispatcher):\n def __init__(self):\n asyncore.dispatcher.__init__(self)\n self.create_socket(socket.AF_INET, socket.SOCK_STREAM)\n self.bind(('', 4915))\n self.listen(1)\n def handle_accept(self):\n sock, addr = self.accept()\n handler = Echoer(sock)\n server = EchoServer()\n asyncore.loop()\n\nAsync I/O lets the thread run a single ``select()`` loop to handle all connections and send *callbacks* when events (such as accepts and data packets) occur. However, the code becomes much more complex: the execution of a simple echo server gets broken up into smaller methods and the control flow becomes hard to follow.\n\nBluelet (like other coroutine-based async I/O libraries) lets you write code that *looks* sequential but *acts* concurrent. Like so::\n\n import bluelet\n def echoer(conn):\n while True:\n data = yield conn.recv(1024)\n if not data:\n break\n yield conn.sendall(data)\n bluelet.run(bluelet.server('', 4915, echoer))\n\nExcept for the ``yield`` keyword, note that this code appears very similar to our first, sequential version. (Bluelet also takes care of the boilerplate socket setup code.) This works because ``echoer`` is a Python coroutine: everywhere it says ``yield``, it temporarily suspends its execution. Bluelet's scheduler then takes over and waits for events, just like asyncore. When a socket event happens, the coroutine is resumed at the point it yielded. So there's no need to break up your code; it can all appear as a single code block. Neat!\n\nOther Examples\n--------------\n\nThis repository also includes a few less-trivial examples of Bluelet's\nprogramming model.\n\nhttpd\n'''''\n\nThe ``httpd.py`` example implements a very simple Web server in less than 100\nlines of Python. Start the program and navigate to\nhttp://127.0.0.1:8088/ in your Web browser to see it\nin action.\n\nThis example demonstrates the implementation of a network server that is\nslightly more complicated than the echo server described above. Again, the code\nfor the server just looks like a sequential, one-connection-at-a-time program\nwith ``yield`` expressions inserted -- but it runs concurrently and can service\nmany requests at the same time.\n\ncrawler\n'''''''\n\n``crawler.py`` demonstrates how Bluelet can be used for *client* code in\naddition to just servers. It implements a very simple asynchronous HTTP client\nand makes a series of requests for tweets from the Twitter API.\n\nThe ``crawler.py`` program actually implements the same set of requests four\ntimes to compare their performance:\n\n* The sequential version makes one request, waits for the response, and then\n makes the next request.\n* The \"threaded\" version spawns one OS thread per request and makes all the\n requests concurrently.\n* The \"processes\" version uses Python's `multiprocessing`_ module to make\n each request in a separate OS process. It uses the multiprocessing module's\n convenient parallel ``map`` implementation.\n* The Bluelet version runs each HTTP request in a Bluelet coroutine. The\n requests run concurrently but they use a single thread in a single process.\n\n.. _multiprocessing: http://docs.python.org/library/multiprocessing.html\n\nThe sequential implementation will almost certainly be the slowest. The three\nother implementations are all concurrent and should have roughly the same\nperformance. The thread- and process-based implementations incur spawning\noverhead; the multiprocessing implementation could see advantages by avoiding\nthe GIL (but this is unlikely to be significant as the network latency is\ndominant); the Bluelet implementation has no spawning overhead but has some\nscheduling logic that may slow things down.\n\n``crawler.py`` reports the runtime of each implementation. On my machine, this\nis what I see::\n\n sequential: 4.62 seconds\n threading: 0.81 seconds\n multiprocessing: 0.13 seconds\n bluelet: 0.20 seconds\n\nThe numbers are noisy and somewhat inconsistent across runs, but in general we\nsee that Bluelet is competitive with the other two concurrent implementations\nand that the sequential version is much slower.\n\nBasic Usage\n-----------\n\nTo get started with Bluelet, you just write a coroutine that yield Bluelet\nevents and invoke it using ``bluelet.run``::\n\n import bluelet\n def coro():\n yield bluelet.end()\n bluelet.run(coro())\n\n``bluelet.run`` takes a generator (a running coroutine) as an argument and runs\nit to completion. It's the gateway into the Bluelet scheduling universe.\nRemember that, in Python, any \"function\" with a ``yield`` expression in it is a\ncoroutine -- that's what makes ``coro`` special.\n\nThe key to programming with Bluelet is to use ``yield`` expressions where you\nwould typically do anything that blocks or you need to interact with the Bluelet\nscheduler. Technically, every ``yield`` statement sends an \"event\" object to the\nBluelet scheduler that's running it, but you can usually get by without thinking\nabout event objects at all. Here are some of the Bluelet ``yield``\nexpressions that make up Bluelet's network socket API:\n\n* ``conn = yield bluelet.connect(host, port)``: Connects to a network host and\n returns a \"connection\" object usable for communication.\n* ``yield conn.send(data)``: Send a string of data over the connection. Returns\n the amount of data actually sent.\n* ``yield conn.sendall(data)``: Send the string of data, continuously sending\n chunks of the data until it is all sent.\n* ``data = yield conn.recv(bufsize)``: Receive data from the connection.\n* ``data = yield conn.readline(delim=\"\\n\")``: Read a line of data from the\n connection, where lines are delimited by ``delim``.\n* ``server = bluelet.Listener(host, port)``: Constructs a Bluelet server\n object that can be used to asynchronously wait for connections. (There's no\n ``yield`` here; this just a constructor.)\n* ``conn = yield server.accept()``: Asynchronously wait for a connection to the\n server, returning a connection object as above.\n\nThese tools are enough to build asynchronous client and server applications with\nBluelet. There's also one convenient off-the-shelf coroutine, called\n``bluelet.server``, that helps you get off the ground with a server application\nquickly. This line::\n\n bluelet.run(bluelet.server(host, port, handler_coro))\n\nruns an asynchronous socket server, listening for concurrent connections. For\neach incoming connection ``conn``, the server calls ``handler_coro(conn))`` and\nadds that coroutine to the Bluelet scheduler.\n\nBluelet also provides some non-socket-related tools encapsulating generic\ngreen-threads capabilities:\n\n* ``res = yield bluelet.call(coro())``: Invokes another coroutine as a\n \"sub-coroutine\", much like calling a function in ordinary Python code.\n Pedantically, the current coroutine is suspended and ``coro`` is started up;\n when ``coro`` finishes, Bluelet returns control to the current coroutine and\n returns the value returned by ``coro`` (see ``bluelet.end``, below). The\n effect is similar to Python's proposed `\"yield from\" syntax`_.\n* ``res = yield coro())``: Shorthand for the above. Just yielding any generator\n object is equivalent to using ``bluelet.call``.\n* ``yield bluelet.spawn(coro())``: Like ``call`` but makes the child coroutine\n run concurrently. Both coroutines remain in the thread scheduler. This is how\n you can build programs that, for example, handle multiple network connections\n at once (it's used internally by ``bluelet.server``).\n* ``yield bluelet.join(coro)``: Suspends the current coroutine until a given\n thread, previously started with ``spawn``, completes.\n* ``yield bluelet.kill(coro)``: Aborts and unschedules a previously-spawned\n thread.\n* ``yield bluelet.end(value=None)``: Terminate the current coroutine and, if the\n present coroutine was invoked by another one using ``bluelet.call``, return\n the specified value to it. Analogous to ``return`` in ordinary Python.\n* ``yield bluelet.sleep(duration)``: Suspend the current coroutine for\n approximately ``duration`` seconds, resuming it at the earliest opportunity\n after the interval has passed.\n* ``yield bluelet.null()``: Yield without doing anything special. This just\n makes it possible to let another coroutine run if one is waiting to. It's\n useful if you have to do a long-running, blocking operation in a coroutine and\n want to give other green threads a chance to get work done.\n\n.. _\"yield from\" syntax: http://www.python.org/dev/peps/pep-0380/\n\nTogether, this small set of ``yield`` statements are enough to build any\napplication that can benefit from simple, pure-Python collaborative\nmultitasking.\n\nAuthors\n-------\n\nBluelet is by `Adrian Sampson`_. Please contact me if you have questions or\ncomments about Bluelet.\n\n.. _Adrian Sampson: http://github.com/sampsyo/", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/sampsyo/bluelet", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "bluelet", "package_url": "https://pypi.org/project/bluelet/", "platform": "ALL", "project_url": "https://pypi.org/project/bluelet/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/sampsyo/bluelet" }, "release_url": "https://pypi.org/project/bluelet/0.2.0/", "requires_dist": null, "requires_python": null, "summary": "pure-Python asynchronous I/O using coroutines", "version": "0.2.0" }, "last_serial": 660149, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "3dabe0c702b13798a82805cf5f234079", "sha256": "7fe0d3034d648f19a60304d710b68a7b9ed710f0b3c6c5f7fa0c8fe3d935912b" }, "downloads": -1, "filename": "bluelet-0.1.tar.gz", "has_sig": false, "md5_digest": "3dabe0c702b13798a82805cf5f234079", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9328, "upload_time": "2011-11-13T03:03:25", "url": "https://files.pythonhosted.org/packages/ac/17/c704aeec749c359883ac7ef66b8c068b84e1f304ea4c843505f9eea0332d/bluelet-0.1.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "130228d6d07a6e0c5b4769eead7fb6fb", "sha256": "b1b85b6b819d9c31ece70af09226d404e7b838737cdc8a47cd8ab7b43f2d8e0c" }, "downloads": -1, "filename": "bluelet-0.2.0.tar.gz", "has_sig": false, "md5_digest": "130228d6d07a6e0c5b4769eead7fb6fb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10851, "upload_time": "2013-03-15T20:20:01", "url": "https://files.pythonhosted.org/packages/0b/4e/1a1688bb660ed9012c94099b8d7cbc26cd57b636259fe959cecb42c52ab0/bluelet-0.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "130228d6d07a6e0c5b4769eead7fb6fb", "sha256": "b1b85b6b819d9c31ece70af09226d404e7b838737cdc8a47cd8ab7b43f2d8e0c" }, "downloads": -1, "filename": "bluelet-0.2.0.tar.gz", "has_sig": false, "md5_digest": "130228d6d07a6e0c5b4769eead7fb6fb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10851, "upload_time": "2013-03-15T20:20:01", "url": "https://files.pythonhosted.org/packages/0b/4e/1a1688bb660ed9012c94099b8d7cbc26cd57b636259fe959cecb42c52ab0/bluelet-0.2.0.tar.gz" } ] }