{ "info": { "author": "Jonathan GAYVALLET", "author_email": "jonathan.mael.gayvallet@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: BSD", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython" ], "description": "[![pipeline status](https://gitlab.com/python-actorio/actorio/badges/master/pipeline.svg)](https://gitlab.com/python-actorio/actorio/commits/master)\n[![coverage report](https://gitlab.com/python-actorio/actorio/badges/master/coverage.svg)](https://gitlab.com/python-actorio/actorio/commits/master)\n[![PyPI version](https://badge.fury.io/py/actorio.svg)](https://badge.fury.io/py/actorio)\n# Actorio - a simple actor framework for asyncio\n\nActorio is a Python asyncio implementation of the [actor model](https://en.wikipedia.org/wiki/Actor_model).\n\nThere already are Python actor model implementation such as\n[Thespian](https://github.com/kquick/Thespian) or [Pykka](https://github.com/jodal/pykka)\nbut they're currently lacking asyncio support.\n\nThe main goal of the Actor model is to cleanly define the critical section \nwithout having to deal with lock or any other synchronization mechanism.\n\nIt also helps with the [Single responsibility principle](https://en.wikipedia.org/wiki/Single_responsibility_principle),\neach `Actor` class should deal with one part of the fonctionnal requirements of the application and its API can be properly defined\n(ie, what kind of message does this actor accept and what kind of message does it produce), hence making actor testing easier,\nsince you don't have to mock the surrounding systems and just check if the message sequence if correct. \n\n\n*Current API is crude and provisional and is likely to change as syntax and concepts evolve.*\n\n\n### Rules of the actor model\n- An actor is an execution unit that executes concurrently with other actors.\n\n- An actor does not share state with anybody else, but it can have its own state.\n\n- An actor can only communicate with other actors by sending and receiving messages. It can only send messages to actors whose address it has.\n\n- When an actor receives a message it may take actions like:\n\n - altering its own state, e.g. so that it can react differently to a future message\n - sending messages to other actors\n - starting new actors\n\n None of the actions are required, and they may be applied in any order.\n\n- An actor only processes one message at a time. In other words, a single actor does not give you any concurrency, and it does not need to use locks internally to protect its own state.\n\n### Hello World\nA Hello world example:\n\nLet's start with the typical example of Hello World. In this case, we'll create an Actor, send it a message, wait for a response message, and print that response: \n\n```python\nfrom actorio import Actor, Message, DataMessage, ask\nimport asyncio\nclass Greeter(Actor):\n # Here we override the handle_message method to send a `DataMessage` with the data \"Hello World!\".\n async def handle_message(self, message: Message):\n await message.sender.tell(DataMessage(data=\"Hello World!\", sender=self))\n\n\nasync def main():\n # Let's create an instance of a Greeter actor and start it. \n async with Greeter() as greeter:\n # Then we'll just send it an empty message and wait for a response\n reply : DataMessage = await ask(greeter, Message())\n print(reply.data)\n\nasyncio.get_event_loop().run_until_complete(main())\n```\n\n### Actor spawning actors\nActor instances can spawn other actors during their execution. be it at startup, in the `mainloop_setup` async method or as a reaction to an event in any handler.\n\nThe actor should first be instanciated (with an `__init__` call) then be registered on its parent with a `register_child` async call.\n\nWhenever a child dies, the parent `handle_child_stopped` async method is called with the child actor object and the `asyncio.task` object for its execution loop. This way, the parent can take any action required.\n\nFor example, if we had to handle blocking operations. we could do something like:\n```python\nimport random\nimport asyncio\nfrom actorio import Actor, Message, DataMessage, EndMainLoop, Reference\nasync def blocking_operation():\n #for the sake of this example, the blocking operation is a sleep\n sleep_time = random.randint(0,10)\n await asyncio.sleep(sleep_time)\n return sleep_time\n\nclass RequestMessage(Message):\n \"\"\"\n A request to do some computation or blocking call\n \"\"\"\n\nclass ResponseMessage(DataMessage):\n \"\"\"\n The result of a computation\n \"\"\"\n\nclass Worker(Actor):\n # Here we override the handle_message method to send a `ResponseMessage`\n # with the result of the blocking operation as its data.\n async def handle_message(self, message: Message):\n sleep_time = await blocking_operation()\n await message.sender.tell(ResponseMessage(data=sleep_time, sender=self))\n # This actor only deals with one message then stops,\n # raising an EndMainLoop exeception here will properly stop the actor\n raise EndMainLoop() \n\nclass Manager(Actor):\n async def handle_message(self, message: Message):\n if isinstance(message, RequestMessage):\n # We spawn and register the new child, we get its reference back\n child = await self.register_child(Worker())\n # We just transfer the message to the child, that way,\n # we won't have to process its response\n await child.tell(message)\n\nasync def main():\n # We create an inbox for us, this is not an actor,\n # just somewhere actors can send messages\n me = Reference()\n async with Manager() as manager:\n # Then we start 10 long computations\n for _ in range(10): await manager.tell(RequestMessage(sender=me))\n # Then we'll listen to our inbox\n # to get the responses as they come by\n for _ in range(10):\n message = await me.inbox.get()\n print(\"Got a response with result {}\".format(message.data))\n\nasyncio.get_event_loop().run_until_complete(main())\n\n```\n### Handling external blocking events\nUsing message is great, but, most of the time, we also need to use other APIs that don't provide a message-based interface.\n\nIt's possible to register external blocking event and handler for those with the `register_input_task` method.\nThis methods takes a factory function for a coroutine and an async function to handle the task result.\n\n**The handler will not be called if the Actor is currently busy processing anything else\n(like a message or any other task), this way, there is no concurrency issue\nand each handler is called with a clean actor state**\n\n*For now, the order in which those events will be handled is not necessarily the order in which they happened* \n\nFor example, to use an `aiohttp` websocket :\n```python\nimport aiohttp\nfrom aiohttp import web\nfrom actorio import Actor, EndMainLoop\nimport asyncio\n\nclass Client(Actor):\n def __init__(self, websocket: web.WebSocketResponse):\n self.websocket = websocket\n super().__init__()\n\n # Here we define an input task handler,\n # It will be called each time its registered event happens.\n # The resulting `asyncio.Task` object will be passed as argument,\n # this way, the handler can deal with any exception raised during event collection \n async def handle_websocket_event(self, task: asyncio.Task):\n try:\n msg = task.result()\n except Exception as e:\n # In case of any exception, we just stop the Actor\n self.logger.exception(e)\n raise EndMainLoop()\n if msg.type == aiohttp.WSMsgType.TEXT:\n # if we receive text, we just send it back\n # We could also just send a message to our inbox\n await self.websocket.send_str(msg.data)\n else:\n # any other request just stops the Actor\n raise EndMainLoop()\n\n async def mainloop_setup(self):\n self.register_input_task(self.websocket.receive, self.handle_websocket_event)\n await super().mainloop_setup()\n\n\n\nasync def websocket_handler(request):\n ws = web.WebSocketResponse()\n await ws.prepare(request)\n\n client_actor = Client(websocket=ws)\n\n async with client_actor:\n # We until the client's mainloop ends\n await client_actor\n\n return ws\n\napp = web.Application()\napp.add_routes([web.get('/', websocket_handler)])\nweb.run_app(app)\n```\n\nYou can then connect to the websocket and send it some messages, it will act as an echo server.\n\n", "description_content_type": "text/markdown; charset=UTF-8; variant=GFM", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://gitlab.com/python-actorio/actorio", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "actorio", "package_url": "https://pypi.org/project/actorio/", "platform": "", "project_url": "https://pypi.org/project/actorio/", "project_urls": { "Homepage": "https://gitlab.com/python-actorio/actorio" }, "release_url": "https://pypi.org/project/actorio/0.1.4/", "requires_dist": [ "pytz (>=2018.7)" ], "requires_python": ">=3.6", "summary": "A simple actor framework for asyncio", "version": "0.1.4" }, "last_serial": 4568835, "releases": { "0.0.0.dev0": [ { "comment_text": "", "digests": { "md5": "7260838464aab11489765bd6bd548158", "sha256": "1ec9d4a011f5e4250f8fd306eea8089b7a508ebae7c2704a0e7f06df4494e97a" }, "downloads": -1, "filename": "actorio-0.0.0.dev0-py3-none-any.whl", "has_sig": false, "md5_digest": "7260838464aab11489765bd6bd548158", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 5291, "upload_time": "2018-11-27T09:53:47", "url": "https://files.pythonhosted.org/packages/ec/10/97ec89c3a4ee8dc217d3d1650cc5855fa7b8f5462896032647379b46dd64/actorio-0.0.0.dev0-py3-none-any.whl" } ], "0.0.0.dev1": [ { "comment_text": "", "digests": { "md5": "228ce4f26d9f5d0ae5f81f87d95bf2f4", "sha256": "f9ef0150c6b25dabd8d5495569a4b6c90a3820588a0294c1fb4a344a7875b194" }, "downloads": -1, "filename": "actorio-0.0.0.dev1-py3-none-any.whl", "has_sig": false, "md5_digest": "228ce4f26d9f5d0ae5f81f87d95bf2f4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 5317, "upload_time": "2018-11-27T10:46:40", "url": "https://files.pythonhosted.org/packages/60/49/a2c387cf42c913a9ceb092ea093649a12c2b09fc0efa7cd6bedbbf6010b3/actorio-0.0.0.dev1-py3-none-any.whl" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "f0f053fd2191456a07f88a6d0967391f", "sha256": "d8604ac313625a93585bb2d0b54598b2c529cbac695be412c5eec35b6a344d9c" }, "downloads": -1, "filename": "actorio-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "f0f053fd2191456a07f88a6d0967391f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 12311, "upload_time": "2018-12-04T13:51:15", "url": "https://files.pythonhosted.org/packages/46/c5/452cdbd860e84a0984eb3a70e6916444da9b27eea9e518b12d41181f5de5/actorio-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ae812077f42c5b5c6752aee966050a43", "sha256": "725af2c27153214573d8041129be1a8e3502a929bba458b1bc9b465ae90f3b65" }, "downloads": -1, "filename": "actorio-0.1.2.tar.gz", "has_sig": false, "md5_digest": "ae812077f42c5b5c6752aee966050a43", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16704, "upload_time": "2018-12-04T13:51:18", "url": "https://files.pythonhosted.org/packages/87/32/adc241c705db1717ff53a89c7ec05f8cfe83deaeb83632b6b626999cd9cd/actorio-0.1.2.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "e3b44f12b13b3ccd0acb6b9d6af92144", "sha256": "23775b51600d1754088038fe5dc75ec1509d3f1513c09e0bc0a6b22fe788aa8a" }, "downloads": -1, "filename": "actorio-0.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "e3b44f12b13b3ccd0acb6b9d6af92144", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 12845, "upload_time": "2018-12-06T17:40:54", "url": "https://files.pythonhosted.org/packages/89/7d/64b3d71380994bde43551f3ea2b65154e46587f2f61181faf6be30eab7a5/actorio-0.1.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3370eb4cf52ae2199e4c9cf333cf4246", "sha256": "64ba524cde541039c0bc0a1b472ecb0f4ba98393e8793e44d1548b37576b5807" }, "downloads": -1, "filename": "actorio-0.1.4.tar.gz", "has_sig": false, "md5_digest": "3370eb4cf52ae2199e4c9cf333cf4246", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 17530, "upload_time": "2018-12-06T17:40:55", "url": "https://files.pythonhosted.org/packages/c1/ab/58954a26014d6837e60b690b53a1c2007e80acf6fca854359b02edfe172f/actorio-0.1.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "e3b44f12b13b3ccd0acb6b9d6af92144", "sha256": "23775b51600d1754088038fe5dc75ec1509d3f1513c09e0bc0a6b22fe788aa8a" }, "downloads": -1, "filename": "actorio-0.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "e3b44f12b13b3ccd0acb6b9d6af92144", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 12845, "upload_time": "2018-12-06T17:40:54", "url": "https://files.pythonhosted.org/packages/89/7d/64b3d71380994bde43551f3ea2b65154e46587f2f61181faf6be30eab7a5/actorio-0.1.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3370eb4cf52ae2199e4c9cf333cf4246", "sha256": "64ba524cde541039c0bc0a1b472ecb0f4ba98393e8793e44d1548b37576b5807" }, "downloads": -1, "filename": "actorio-0.1.4.tar.gz", "has_sig": false, "md5_digest": "3370eb4cf52ae2199e4c9cf333cf4246", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 17530, "upload_time": "2018-12-06T17:40:55", "url": "https://files.pythonhosted.org/packages/c1/ab/58954a26014d6837e60b690b53a1c2007e80acf6fca854359b02edfe172f/actorio-0.1.4.tar.gz" } ] }