{ "info": { "author": "Justin Engel", "author_email": "jtengel08@gmail.com", "bugtrack_url": null, "classifiers": [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3" ], "description": "# mp_event_loop\n\nLibrary for long running multiprocessing event loops. This library provides an EventLoop that will run events in a \nseparate process. The purpose of this library is to manage a long running process while a GUI is running in the main \nthread. Tasks can be offloaded to another processes continuously while the GUI is running. \n\n**Now works with async/await!** see the example below.\n\nThe EventLoop comes with several utilities for managing the separate process.\n\n * is_running() - Return if the separate process is running\n * start() - Start the event loop\n * run(events=None, output_handlers=None) - Add the output handlers and run the events\n * run_until_complete(events=None, output_handlers=None) - Run the given events and wait until they are finished\n * wait() - Wait for the current events to finish\n * stop() - Stop the event loop\n * close() - Close the event loop\n * _\\_enter_\\_ and _\\_exit_\\_ - works as a context manager and allows use of the `with` statement \n\nThe EventLoop also comes with some utilities to add commands to be processed and a way to handle results.\n\n * add_event(target, args, kwargs, ...) - Add an event to be executed in a separate process.\n * add_output_handler(function) - Function that takes in an Event after the event has been executed.\n \nThese functions will be explained more below\n\n\n## Quick Start\n\n pip install mp_event_loop\n \n```python\nimport mp_event_loop\n\n\ndef add_vals(value, value2=1):\n return value + value2\n \n \nresults = []\n\ndef save_results(event):\n results.append(event.results)\n\n\nwith mp_event_loop.get_event_loop(save_results):\n mp_event_loop.add_event(add_vals, 2)\n mp_event_loop.add_event(add_vals, 3, 4)\n mp_event_loop.add_event(add_vals, 5, value2=6)\n mp_event_loop.add_event(add_vals, args=(7,), kwargs={'value2': 8})\n \n# with context manager waits for events and event results to finish until it exits\nassert results == [3, 7, 11, 15]\nsummed = sum(results)\nassert summed == 36\nprint(\"Results summed:\", summed)\n# Results summed: 36\n```\n\nAlternative approach\n```python\nimport mp_event_loop\n\ndef add_one(value):\n return value + 1\n \n \nresults = []\n\ndef save_results(event):\n results.append(event.results)\n \nmp_event_loop.run([{'target': add_one, 'args': (1,)},\n {'target': add_one, 'args': (2,)},\n {'target': add_one, 'args': (3,)},\n ], save_results)\n \nmp_event_loop.add_event(add_one, 4)\n\nmp_event_loop.wait()\n\nprint(\"Results summed:\", sum(results))\n# Results summed: 14\n```\n## Async / Await\n\nYou must register your coroutines with the AsyncManager in order to run in a separate process. Coroutines are not \npicklable, so they must be registered at the module level. During unpickling (_\\_setstate_\\_) the coroutine will be \nretrieved by the registered name using the AsyncManager.\n\n\n```python\nfrom mp_event_loop import AsyncEventLoop, AsyncManager\n\n\nasync def print_test(value, name):\n print(\"Print\", name)\n return value\n\n\nasync def yield_range(value, name):\n print(\"Yield\", name)\n for i in range(value):\n yield name + \" \" + str(i)\n\n\n# Do not need to register anymore\n# AsyncManager.register('print_test', print_test)\n# AsyncManager.register('yield_range', yield_range)\n\n\nif __name__ == '__main__':\n results = []\n\n def save_results(event):\n results.append(event.results)\n\n with AsyncEventLoop(output_handlers=save_results) as loop:\n loop.async_event(print_test, 1, \"hello\")\n loop.async_event(print_test, 2, \"hi\")\n loop.async_event('print_test', 3, \"oi\") # Can also use the registered name\n loop.async_event(yield_range, 5, 'first')\n loop.async_event(yield_range, 5, 'second')\n\n print(results)\n # [1, 2, 3, 'first 0', 'first 1', 'second 0', 'first 2', 'second 1', 'first 3', 'second 2', 'first 4', 'second 3', 'second 4']\n\n```\n\n## How it works\nThe EventLoop works by creating a Process and a Thread. The Process takes the \nEvent from a queue and runs the function. Once the Event is complete, the event is put on a result Queue/consumer Queue. \nThe Thread takes the Event from the result Queue and passes it to all of the output_handlers in the EventLoop. \nIf one of the output_handlers returns True the event will stop propagating to the other output_handlers.\n\nBecause of locking mechanisms in the Queue and message passing between processes this will be slow. You will probably \nonly use this for concurrency. This is usefully for non-IO concurrency where Threads may impact performance.\n\nI created this library as a test to understand how multiprocessing works. I am attempting to use multiprocessing for \ntcp communication and parsing data. I want the parsing to happen in a separate process, but I want to access the \nparsed data in a thread allowing a GUI to run.\n\n\n## Object Persistence\nThe overall goal for this library is a long running process where tasks can run easily. Along with this is object \npersistence. You can easily send an object to a separate process and run a task on it. The difficult part is getting\nthe values from that object back. The multiprocessing library already as a good way of doing this through the Manager \nclass. However, I found this approach difficult to use with PySide.\n\n```python\n# example problem\nimport mp_event_loop\n\n\nclass ABC(object):\n def __init__(self, a, b, c):\n self.a = a\n self.b = b\n self.c = c\n\n def calc_c(self):\n self.c = self.a + self.b\n\n def __repr__(self):\n return \"ABC(a=%d, b=%d, c=%d)\" % (self.a, self.b, self.c)\n \n\na = ABC(1, 2, 0)\n\nwith mp_event_loop.get_event_loop(has_results=False) as loop:\n loop.add_event(print, \"=====\", a, \"=====\") # Prints a correctly \"ABC(a=1, b=2, c=0)\"\n loop.add_event(a.calc_c) # Does calculation, but doesn't send the result back to us\n # The other process has the correct c value but never passes it back to the correct object\n\n # We are passing this a's values down which is 1, 2, 0. We never got the result of \"a.calc_c()\"\n loop.add_event(print, a) # Prints the 'a' that the main thread passed to the process \"ABC(a=1, b=2, c=0)\"\n```\n\nThis library tries to solve this problem in two ways.\n\nThe first way I label as caching. The CacheEvent solves this by saving a reference to an object in a dictionary. \nThe object is pickled to a separate process one time and saved. Every subsequent CacheEvent uses a key (id) to \nretrieve the cached object and run in the other process with that cached object\n\nThe second way is through a proxy where an object only exists in the other process. My approach to a proxy has the \nmain process keep a reference to a simple object. When that object is pickled it creates an object in the other \nprocess. It creates this object once then uses caching to save a reference to that object. The object only lives in \nthe other process. You can controll the object by calling function in the main process which really just sends an \nevent to run on the other process. If you want an attribute value or getter method value exposed on the main process \nthen you need to define `PROPERTIES` and `GETTERS`. Note: this is merely a solution I found to work with PySide and \ncreating widgets dynamically in another process. I plan on having another library named qt_concurrency to handle the \nspecifics between long running multiprocessing and Qt.\n\n\n### CacheEvent\nThe above example shows that `a` in the main process is never changing. `a.calc_c()` is run the other process, but the \nother process does not save the object `a`. In the scope of the other process `a` with `a.calc_c()` dies and goes away.\nThe main process `a` never changed and still has the values of a=1, b=2, c=0.\n\nTo solve this problem I created some caching events. These events save object with an id. The other process is given \nthe object_id and uses it to get the correct object.\n\n```python\na = ABC(1, 2, 0)\n\nwith mp_event_loop.get_event_loop(has_results=False) as loop:\n loop.add_event(print, \"=====\", a, \"=====\") # Prints a correctly \"ABC(a=1, b=2, c=0)\"\n \n # You can manually cache an object\n # loop.cache_object(a)\n # Only needed for object arguments 'loop.add_event(my_func, a, cache=True)' only my_func is registered and cached.\n # If 'a' is cached it will use it by using a's object_id, but it will not register and cache the 'a' object. \n \n # Or pass in cache=True or loop.add_cache_event\n loop.add_event(a.calc_c, cache=True) # Keeps track of an id for 'a' and saves 'a' in the separate process\n \n # DO NOT pass a down to the other process. Instead pass an object_id for 'a'. The other process will use the \n # object_id to get the stored 'a' object and use that object.\n loop.add_event(print, a, cache=True) # Prints the cached 'a' object correctly \"ABC(a=1, b=2, c=3)\"\n```\n\nWhile this now works like we want it to there is still a problem. `a` in the main process does not have the correct \nvalues. You can solve this with tedious event handling shown below.\n\n```python\nimport mp_event_loop\n\na = ABC(1, 2, 0)\n\n\ndef save_object(event):\n if event.event_key == 'a':\n a.a = event.results.a\n a.b = event.results.b\n a.c = event.results.c\n return True\n\n\nwith mp_event_loop.get_event_loop(output_handlers=save_object) as loop:\n loop.add_event(print, \"=====\", a, \"=====\") # Prints a correctly \"ABC(a=1, b=2, c=0)\"\n loop.add_event(a.calc_c) # Does calculation and sends the event back through the output_handler\n # However we have no way of identifying what event/object was returned back to us (in save_object)\n \n # Try getting the object back with save_result_object\n loop.add_event(a.calc_c, event_key='a') # We can use the event_key to identify the returned event (in save_object)\n print(\"Main process\", a) # The multiprocessing has not happened yet. No immediate results.\n\nprint(\"Main process after event loop\", a) # The loop has now waited for all tasks to complete and results are correct.\n```\n\n### Proxy object\nThis is by far the most challenging part of this library. For my application I don't care about immediate results. I \ncare about concurrency and long running tasks being done in a separate process allowing a GUI to live.\n\nBelow is example code for a Proxy. The proxy object only has access to \n\n```python\nimport mp_event_loop\n\nclass Point(object):\n def __init__(self, x=0, y=0, z=0):\n self.x = x\n self.y = y\n self._z = z\n \n def get_z(self):\n return self._z\n \n def set_z(self, z):\n self._z = z\n\n def move(self, x, y, z):\n self.x = x\n self.y = y\n self._z = z\n \nclass MpPoint(mp_event_loop.Proxy):\n PROXY_CLASS = Point\n PROPERTIES = ['x', 'y']\n GETTERS = ['get_z']\n\n def __init__(self, x=0, y=0, z=0, loop=None):\n super().__init__(loop=loop)\n self.x = x\n self.y = y\n self._z = z\n \n\n\nwith mp_event_loop.EventLoop() as loop:\n p = MpPoint(loop=loop)\n p.set_z(3) # Runs in the other process\n assert p._z != 3 \n assert p.get_z() != 3\n \n # Sends object down to separate process and runs move.\n # The separate process has attributes x and y and uses get_z and set_z to have the correct z value.\n p.move(1, 2, 7)\n assert p.x == 0\n assert p.y == 0\n assert p._z == 3\n assert p.get_z() == 3\n\nassert p.x == 1\nassert p.y == 2\nassert p.get_z() == 7\n```\n\n## Events\nEvents simply take in a function and some arguments and execute them in a separate process. Theoretically, you could\nmake your own event for something specific.\n\n```python\nimport mp_event_loop\n\nclass MyEvent(mp_event_loop.Event):\n def __init__(self, data, **kwargs):\n super().__init__(target=None, args=(data,), **kwargs)\n \n def run(self):\n data = self.args[0]\n \n # Run some calculation\n value = list(range(data))\n\n return value\n \n # def exec_(self):\n # \"\"\"Get the command and run it\"\"\"\n # # Get the command to run\n # self.results = None\n # self.error = None\n # if callable(self.target):\n # # Run the command\n # try:\n # self.results = self.run()\n # except Exception as err:\n # self.error = err\n # elif self.target is not None:\n # self.error = ValueError(\"Invalid target (%s) given! Type %s\" % (repr(self.target), str(type(self.target))))\n\n\ndef print_results(event):\n print(event.results)\n\n\nloop = mp_event_loop.EventLoop(output_handlers=print_results)\n\nloop.start()\n\nloop.add_event(MyEvent(10))\n\nloop.stop()\n\n# At some point [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] should be printed\n```\n\nIt is much easier to pass a function into an Event, but you may find this useful.\n\n\n## Output Handlers\n\nThe EventLoop contains a list of output_handlers. An output handler is just a simple function that takes in an event.\nThe Event object will have results property which contains the results from the event execution (results return \nfrom the target function).\n\n```python\nimport mp_event_loop\n\nclass MyEvent(mp_event_loop.Event):\n def __init__(self, data, **kwargs):\n super().__init__(target=None, args=(data,), **kwargs)\n \n def run(self):\n data = self.args[0]\n \n # Run some calculation\n value = list(range(data))\n\n return value\n \n \ndef print_my_event(event):\n if isinstance(event, MyEvent):\n print('My Event', event.results)\n return True # Stop running the other output_handlers\n else:\n print(\"Not My Event\")\n\ndef print_event(event):\n print(\"Normal Event\", event.results)\n \n \ndef add_one(value):\n return value + 1\n \n \nwith mp_event_loop.EventLoop(output_handlers=[print_my_event, print_event]) as loop:\n loop.add_event(target=add_one, args=(1,))\n loop.add_event(target=add_one, args=(2,))\n loop.add_event(MyEvent(3))\n loop.add_event(target=add_one, args=(4,))\n loop.add_event(MyEvent(5))\n\n\n# Not My Event\n# Normal Event 2\n# Not My Event\n# Normal Event 3\n# My Event [0, 1, 2]\n# Not My Event\n# Normal Event 5\n# My Event [0, 1, 2, 3, 4]\n```\n\n## Pickling\nIf pickling is annoying you then you can use a different multiprocessing library.\n\nThe EventLoop uses 4 class variables to create the proper Process and Thread objects\n\n * EventLoop.alive_event_class = multiprocessing.Event\n * EventLoop.queue_class = multiprocessing.JoinableQueue\n * EventLoop.event_loop_class = multiprocessing.Process\n * EventLoop.consumer_loop_class = threading.Thread\n\nThe `use` function has been provided to make this process easier.\n\n```python\nimport mp_event_loop\n\nimport threading\nimport multiprocess as mp\n\nmp_event_loop.use(mp) # This does not change the consumer_loop_class\n# mp_event_loop.use('multiprocess') # Works for string arguments as well\n\n# Or\n\n# Below does the same as 'use'\nmp_event_loop.EventLoop.alive_event_class = mp.Event\nmp_event_loop.EventLoop.queue_class = mp.JoinableQueue\nmp_event_loop.EventLoop.event_loop_class = mp.Process\nmp_event_loop.EventLoop.consumer_loop_class = threading.Thread\n```\n\n### Pickling Problems\nMy goal was to extend this library to work with async/await. Unfortunately, coroutines and generators cannot be \npickled. \n\nInitially the async/await event loop failed because generators cannot be pickled. I wanted to create another event \nloop where function with the yield statement would allow other Events to run. While generators cannot be pickled, \nit is possible to create a class with _\\_iter_\\_ and _\\_next_\\_ methods which can be pickled. I created an event \nloop (iter_event_loop.IterEventLoop) which will collect iterators and interleave iterators. After _\\_next_\\_ is called \na different iterator will execute adding more concurrency. This only makes it so long running iterators do not take \nup all of the processing time and lets other iterator events run in between iterations.\n\nI was able to get async/await to work in multiprocessing. I used the same technique that I used for caching which was\nto register the async functions in a class dictionary and pickle the registered name. At unpickling the registered name\nis used to retrieve the async function and run it in the other process.", "description_content_type": "", "docs_url": null, "download_url": "https://github.com/justengel/mp_event_loop/archive/v1.5.2.tar.gz", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/justengel/mp_event_loop", "keywords": "multiprocessing event loop event loop process async await", "license": "Proprietary", "maintainer": "", "maintainer_email": "", "name": "mp-event-loop", "package_url": "https://pypi.org/project/mp-event-loop/", "platform": "any", "project_url": "https://pypi.org/project/mp-event-loop/", "project_urls": { "Download": "https://github.com/justengel/mp_event_loop/archive/v1.5.2.tar.gz", "Homepage": "https://github.com/justengel/mp_event_loop" }, "release_url": "https://pypi.org/project/mp-event-loop/1.5.2/", "requires_dist": null, "requires_python": "", "summary": "Library for long running multiprocessing event loops.", "version": "1.5.2" }, "last_serial": 4458318, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "2e46e64e9cdf53301954d41b16e5c519", "sha256": "f4408007869ad32c28eb108b9ceaf9f2f399a9006cbce3e9acbba930133219ed" }, "downloads": -1, "filename": "mp_event_loop-0.0.1.tar.gz", "has_sig": false, "md5_digest": "2e46e64e9cdf53301954d41b16e5c519", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9760, "upload_time": "2018-06-27T15:21:47", "url": "https://files.pythonhosted.org/packages/03/09/61a70d02f29f2e1e3ddcd6fcfe3faa6aaf2fb1d35d2be9ddb3e68538f0ef/mp_event_loop-0.0.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "82ccd2a6b2bb78fc7a2ea4ca099e9b4d", "sha256": "f7e4aac8015967c40ff9d3005ce703ee2bfb7f1908d41be1e6875cb8a8a6b982" }, "downloads": -1, "filename": "mp_event_loop-1.0.0.tar.gz", "has_sig": false, "md5_digest": "82ccd2a6b2bb78fc7a2ea4ca099e9b4d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11134, "upload_time": "2018-06-27T20:28:47", "url": "https://files.pythonhosted.org/packages/03/68/4e56a053d3a0ca58529c999aed855625244bb50cfa899da911321015c797/mp_event_loop-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "7a9c1cf82668e1f5fcf088ccdc3d7a72", "sha256": "a95a6fd9e1f357e089ea8de0ab2431bd889679ee39e5b266671fef75b19d8084" }, "downloads": -1, "filename": "mp_event_loop-1.0.1.tar.gz", "has_sig": false, "md5_digest": "7a9c1cf82668e1f5fcf088ccdc3d7a72", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11132, "upload_time": "2018-06-27T20:30:55", "url": "https://files.pythonhosted.org/packages/fb/b4/a5d89648de068cb786194dd0d4381e61f2ce89719f4e8aa49e48dcf14e82/mp_event_loop-1.0.1.tar.gz" } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "2f00e3c73ac9f6c9f08a51c378057f18", "sha256": "77d4b98318cc4102936133893e8da836ce2e8b8f38d75a9c7affe280b161418f" }, "downloads": -1, "filename": "mp_event_loop-1.0.2.tar.gz", "has_sig": false, "md5_digest": "2f00e3c73ac9f6c9f08a51c378057f18", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11134, "upload_time": "2018-06-27T20:32:44", "url": "https://files.pythonhosted.org/packages/05/d5/3c089d3e4b2f5f9bbccd79d761d5ccf19c3d0be91336e26f4a42c4fa8bd7/mp_event_loop-1.0.2.tar.gz" } ], "1.0.3": [ { "comment_text": "", "digests": { "md5": "88cd027cfd6030729e686515aa4b89ac", "sha256": "4d0e47ec0400c0081b954f142adf815fa9c879d59924089958e2919631e5b838" }, "downloads": -1, "filename": "mp_event_loop-1.0.3.tar.gz", "has_sig": false, "md5_digest": "88cd027cfd6030729e686515aa4b89ac", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11522, "upload_time": "2018-06-27T20:49:22", "url": "https://files.pythonhosted.org/packages/5e/d0/d067cf159ce8d2ff696b558276ad53b5776b304216deb5fffaa2b78edf3d/mp_event_loop-1.0.3.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "3aa33f77a4e1fde9b3d83ae7910878ce", "sha256": "cf306929606f8d6749dc1a10feca712dfa63ce6956c3e54a23405152d40b814d" }, "downloads": -1, "filename": "mp_event_loop-1.1.0.tar.gz", "has_sig": false, "md5_digest": "3aa33f77a4e1fde9b3d83ae7910878ce", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12677, "upload_time": "2018-06-28T19:11:54", "url": "https://files.pythonhosted.org/packages/a5/b1/2a48efb91946b0cd677716ec561a46b050d21b9c0c30693d3b5312fce955/mp_event_loop-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "8b8f0f7f9ec995c45ac297657ea16e0a", "sha256": "505b920c47ddd75fc35d399d6d6ee8e12d5ca6557c1a144733b0c3ef0a2da62a" }, "downloads": -1, "filename": "mp_event_loop-1.1.1.tar.gz", "has_sig": false, "md5_digest": "8b8f0f7f9ec995c45ac297657ea16e0a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12669, "upload_time": "2018-06-28T19:14:20", "url": "https://files.pythonhosted.org/packages/33/39/836d711963e5e6992b1ff2c9170e7fcdcd9c73fb7f266308234a81dbf3e9/mp_event_loop-1.1.1.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "f11b32caab231044819d8dc7e63b9dcc", "sha256": "bcd07ca1f7509868f963c6bdb6b4beef10a734dd691de691196e72a8e163826c" }, "downloads": -1, "filename": "mp_event_loop-1.1.2.tar.gz", "has_sig": false, "md5_digest": "f11b32caab231044819d8dc7e63b9dcc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15300, "upload_time": "2018-06-29T18:41:17", "url": "https://files.pythonhosted.org/packages/e0/3f/bc5fd0e564c5dd828914fc36a2ed3f25a8cd3d45186dc00a009eb964ec30/mp_event_loop-1.1.2.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "e1e0ff5824c7b1fad1504e8e18313d61", "sha256": "d7af689131e814f6d944950b4751f22b621f04f1c19db44028fe33debe7ac37e" }, "downloads": -1, "filename": "mp_event_loop-1.2.0.tar.gz", "has_sig": false, "md5_digest": "e1e0ff5824c7b1fad1504e8e18313d61", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15529, "upload_time": "2018-06-29T19:27:25", "url": "https://files.pythonhosted.org/packages/81/ff/8f40d8cb7c947adeaa3f2cb980e16666c8533e10674b2fb8507f00c58d97/mp_event_loop-1.2.0.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "32c532b40e66ca3c00c26487b68577c1", "sha256": "507662d45b9662424e5efaad1a70bb67e8962fdb201c78bfe41a3653d484d07e" }, "downloads": -1, "filename": "mp_event_loop-1.2.1.tar.gz", "has_sig": false, "md5_digest": "32c532b40e66ca3c00c26487b68577c1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15667, "upload_time": "2018-07-02T12:29:49", "url": "https://files.pythonhosted.org/packages/6b/f1/b09293455b8c6f9fd87f854a07723b649dd2da026f3103a603b2fe43a0b7/mp_event_loop-1.2.1.tar.gz" } ], "1.2.2": [ { "comment_text": "", "digests": { "md5": "b5ef23d1496cb5b245e864841445cf4b", "sha256": "155381d9611e4141f7adbeea5c2d37e1409777ed39c84b5176011805d65df263" }, "downloads": -1, "filename": "mp_event_loop-1.2.2.tar.gz", "has_sig": false, "md5_digest": "b5ef23d1496cb5b245e864841445cf4b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15678, "upload_time": "2018-07-02T13:23:39", "url": "https://files.pythonhosted.org/packages/cf/85/de566a5b2977b03c0201e29f4526265dddc62c107f01b0081af87e69726a/mp_event_loop-1.2.2.tar.gz" } ], "1.2.4": [ { "comment_text": "", "digests": { "md5": "f6637bbed73e7b966b11ec78376428f3", "sha256": "d8da8542c7e88e0a336817ef1b7611cdd9e0b599e6e42ffc9b55c39e55af5624" }, "downloads": -1, "filename": "mp_event_loop-1.2.4.tar.gz", "has_sig": false, "md5_digest": "f6637bbed73e7b966b11ec78376428f3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17928, "upload_time": "2018-07-05T17:03:47", "url": "https://files.pythonhosted.org/packages/79/0c/3ed12064c5a2c91e50cd7073be9d2f25587e8ef82eae697ef5c56a004c5c/mp_event_loop-1.2.4.tar.gz" } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "c3fc7ea905a8372965547381a4f41c1d", "sha256": "bdd6adbc2e7e3386a8d61f673308c9c513d08360ab14c13aa2e05b295fd3792e" }, "downloads": -1, "filename": "mp_event_loop-1.3.0.tar.gz", "has_sig": false, "md5_digest": "c3fc7ea905a8372965547381a4f41c1d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20951, "upload_time": "2018-07-23T13:49:44", "url": "https://files.pythonhosted.org/packages/bb/2c/aa6d96d7482c3cc279e8563d019bd738303554894de6e9209d05bfc5a586/mp_event_loop-1.3.0.tar.gz" } ], "1.3.1": [ { "comment_text": "", "digests": { "md5": "7c1ba750b27a72f2fc7bde96402c3654", "sha256": "8ca32131a7e805936fc967533c49501f2b0b7252cb3ecb4d6f9ad403e84b75e8" }, "downloads": -1, "filename": "mp_event_loop-1.3.1.tar.gz", "has_sig": false, "md5_digest": "7c1ba750b27a72f2fc7bde96402c3654", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20941, "upload_time": "2018-07-23T16:22:53", "url": "https://files.pythonhosted.org/packages/e9/87/44b1cb48c87c52dec8549b680004426d8974cd646da36b74bfe7321a08ad/mp_event_loop-1.3.1.tar.gz" } ], "1.3.2": [ { "comment_text": "", "digests": { "md5": "9dd087c4ce77c1606fe6a3a41afe6427", "sha256": "d875533b324fb7181d78dae102e2b7b7d6af55f756a1b43c4b3435b0929a746f" }, "downloads": -1, "filename": "mp_event_loop-1.3.2.tar.gz", "has_sig": false, "md5_digest": "9dd087c4ce77c1606fe6a3a41afe6427", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20992, "upload_time": "2018-07-23T16:44:54", "url": "https://files.pythonhosted.org/packages/a4/8a/65cb95ca9e07914ba10e3befcd5e0af2f4734fd413c05cd97c5a02fac313/mp_event_loop-1.3.2.tar.gz" } ], "1.3.3": [ { "comment_text": "", "digests": { "md5": "b54faa6499f617011f3acbe66c34854c", "sha256": "cd46bb2aef1935fc79a61d62b97b4c26093c48a14798ff13bd36bf374772c774" }, "downloads": -1, "filename": "mp_event_loop-1.3.3.tar.gz", "has_sig": false, "md5_digest": "b54faa6499f617011f3acbe66c34854c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21001, "upload_time": "2018-07-23T16:49:15", "url": "https://files.pythonhosted.org/packages/f9/c2/157d3f4230265390fa5055234c325394b8426ba627ee97c01714486fbd5a/mp_event_loop-1.3.3.tar.gz" } ], "1.3.4": [ { "comment_text": "", "digests": { "md5": "5868d85c28dff056f01c200f2f090744", "sha256": "3290d7fef7638627c0fbe45675a9ae2aaa8e9eef5162cd49bf8a56554e8886ab" }, "downloads": -1, "filename": "mp_event_loop-1.3.4.tar.gz", "has_sig": false, "md5_digest": "5868d85c28dff056f01c200f2f090744", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21128, "upload_time": "2018-07-23T18:11:54", "url": "https://files.pythonhosted.org/packages/2c/a2/25035dc97a84c0bf2c8666147eaaafeb7dbdc4c1968f16f23f053f25d1df/mp_event_loop-1.3.4.tar.gz" } ], "1.3.5": [ { "comment_text": "", "digests": { "md5": "417872c3075eba65b507b1330f601d14", "sha256": "ab350b0af3a07576342ef062c4222346116e1e4e0a5882bf86d8d446b2e942ff" }, "downloads": -1, "filename": "mp_event_loop-1.3.5.tar.gz", "has_sig": false, "md5_digest": "417872c3075eba65b507b1330f601d14", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21129, "upload_time": "2018-07-23T20:10:02", "url": "https://files.pythonhosted.org/packages/e0/20/9cfafd42a89c708e785437da2e23501a25fa26f2d311f092fd662ab6836f/mp_event_loop-1.3.5.tar.gz" } ], "1.4.0": [ { "comment_text": "", "digests": { "md5": "10e9718353bf9a220b70875450983474", "sha256": "e38038f5a3cd18d4e88ffca2d9008b5b67bee0d340cadc8ba992d5e869941591" }, "downloads": -1, "filename": "mp_event_loop-1.4.0.tar.gz", "has_sig": false, "md5_digest": "10e9718353bf9a220b70875450983474", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21967, "upload_time": "2018-07-24T15:21:08", "url": "https://files.pythonhosted.org/packages/21/33/8b496055a86e0f265bc563c380fcc00267c5bf13bcf3dc3a4e8f09f3fde8/mp_event_loop-1.4.0.tar.gz" } ], "1.4.1": [ { "comment_text": "", "digests": { "md5": "864d8a5d82b2bada38146cd448cdf663", "sha256": "2a40c12bf13f5444c8800b0586ce76a91d16f2b6c5fc14d384310abc297dbf3c" }, "downloads": -1, "filename": "mp_event_loop-1.4.1.tar.gz", "has_sig": false, "md5_digest": "864d8a5d82b2bada38146cd448cdf663", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21975, "upload_time": "2018-07-26T02:42:02", "url": "https://files.pythonhosted.org/packages/92/ca/c1ed92eb3222f65df5658a839af2c60aa554e5d8e618c627076c260c79a0/mp_event_loop-1.4.1.tar.gz" } ], "1.4.10": [ { "comment_text": "", "digests": { "md5": "8f6de1f54c1ba2072ba7b642c8764095", "sha256": "4dca8df7611cbebd4449d3536c1eb2b799546809f0ebaea1ad0bb5df87797021" }, "downloads": -1, "filename": "mp_event_loop-1.4.10.tar.gz", "has_sig": false, "md5_digest": "8f6de1f54c1ba2072ba7b642c8764095", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23852, "upload_time": "2018-09-04T13:38:51", "url": "https://files.pythonhosted.org/packages/52/54/6c49fb06d0f9f2afdd025cf00ad9ff705a26f9073dd844c0d7df67871dfa/mp_event_loop-1.4.10.tar.gz" } ], "1.4.2": [ { "comment_text": "", "digests": { "md5": "baed701caba08cfdb4805576439990a5", "sha256": "6233c191d38ad2b7f39dde74608f62c21e7837ed8ed0796763b27f6e26b58049" }, "downloads": -1, "filename": "mp_event_loop-1.4.2.tar.gz", "has_sig": false, "md5_digest": "baed701caba08cfdb4805576439990a5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21992, "upload_time": "2018-07-26T02:45:34", "url": "https://files.pythonhosted.org/packages/7f/ca/f1c0e81075549ad87ab347c4e075da8953016cfce1d150aa04c59cb759fa/mp_event_loop-1.4.2.tar.gz" } ], "1.4.3": [ { "comment_text": "", "digests": { "md5": "20f599db98984ebb7665e412464da146", "sha256": "f46c42ac0a41b1702796a6a751491054ef5ba32d838a6afc16006af5b9b9b963" }, "downloads": -1, "filename": "mp_event_loop-1.4.3.tar.gz", "has_sig": false, "md5_digest": "20f599db98984ebb7665e412464da146", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22370, "upload_time": "2018-07-26T14:45:41", "url": "https://files.pythonhosted.org/packages/6f/8c/125883772c17e447f02661f058974dfe07a55f39ef21d0b6d7985b0f1892/mp_event_loop-1.4.3.tar.gz" } ], "1.4.4": [ { "comment_text": "", "digests": { "md5": "5a7c63b68ecad1065d47333b94f67488", "sha256": "4defb7426ca7d722be33aacd992f5cc55b88f2e4b90fdd53f32b9f253b29158a" }, "downloads": -1, "filename": "mp_event_loop-1.4.4.tar.gz", "has_sig": false, "md5_digest": "5a7c63b68ecad1065d47333b94f67488", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22456, "upload_time": "2018-07-26T16:43:40", "url": "https://files.pythonhosted.org/packages/27/32/3a4d28fbb9ae2e0622507c1a43fbcd50d819d921cbb7419a9a61658d2039/mp_event_loop-1.4.4.tar.gz" } ], "1.4.5": [ { "comment_text": "", "digests": { "md5": "c89e576ee08a7c24897daca8f6a23e31", "sha256": "30bd4a85ed813d9a9688757da2269fcf022fd7ef0e687219a8f697836e370ee0" }, "downloads": -1, "filename": "mp_event_loop-1.4.5.tar.gz", "has_sig": false, "md5_digest": "c89e576ee08a7c24897daca8f6a23e31", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23528, "upload_time": "2018-07-26T17:39:42", "url": "https://files.pythonhosted.org/packages/6a/09/fdc823d4e90d107502d2a86adef39810d5e20fa5ee70a579f12ef2ff9ab0/mp_event_loop-1.4.5.tar.gz" } ], "1.4.6": [ { "comment_text": "", "digests": { "md5": "8b1341baa598a7d4bd5bafadf79a8c0b", "sha256": "38dc1b5ea5fd9aa2a22ab673746923b1f5f8690c565f4fcc7a8a3c16e2444373" }, "downloads": -1, "filename": "mp_event_loop-1.4.6.tar.gz", "has_sig": false, "md5_digest": "8b1341baa598a7d4bd5bafadf79a8c0b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23570, "upload_time": "2018-07-26T17:54:52", "url": "https://files.pythonhosted.org/packages/30/f7/ce58db09527adadcdb0840008802f00cda0d91f602f6aed765c90371d14f/mp_event_loop-1.4.6.tar.gz" } ], "1.4.7": [ { "comment_text": "", "digests": { "md5": "d8d2b315e267b5d537171565e712b1c3", "sha256": "86136a0301a85229f024c04b4b23ce18d0ac7206b86694403523606b909ff4b5" }, "downloads": -1, "filename": "mp_event_loop-1.4.7.tar.gz", "has_sig": false, "md5_digest": "d8d2b315e267b5d537171565e712b1c3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23572, "upload_time": "2018-07-26T19:50:58", "url": "https://files.pythonhosted.org/packages/87/45/f971951c2b18887ea8f8f099fb650e7ffd1fc165ceb169d9a5210e925475/mp_event_loop-1.4.7.tar.gz" } ], "1.4.8": [ { "comment_text": "", "digests": { "md5": "2d9fb19141623142a0da324b92f72f6a", "sha256": "3af07e787c90313e9f69b4582d5b8911e6181dacfd411db1f7a31ea52407de72" }, "downloads": -1, "filename": "mp_event_loop-1.4.8.tar.gz", "has_sig": false, "md5_digest": "2d9fb19141623142a0da324b92f72f6a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23570, "upload_time": "2018-07-27T18:41:48", "url": "https://files.pythonhosted.org/packages/18/d4/45dde1a6209e7f01a1eca3ac66dff65de20d9537a97e8bbf218cde71f7a5/mp_event_loop-1.4.8.tar.gz" } ], "1.4.9": [ { "comment_text": "", "digests": { "md5": "173482674b4956134144bcc67ba46a10", "sha256": "614a9dedf3b920c6c132ccca8171365a37f677a1ae02fcc8d89d00aad2346291" }, "downloads": -1, "filename": "mp_event_loop-1.4.9.tar.gz", "has_sig": false, "md5_digest": "173482674b4956134144bcc67ba46a10", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23810, "upload_time": "2018-07-30T19:49:13", "url": "https://files.pythonhosted.org/packages/21/2b/9f00644296173af54388ec520933f91f32f76682308496f9b70a99e12d50/mp_event_loop-1.4.9.tar.gz" } ], "1.5.0": [ { "comment_text": "", "digests": { "md5": "3c1b7b5962a5b2ca5beeea9518b26c67", "sha256": "af84389713b26284033bed98de8624104a0d699d2289667a2f56825c3498060b" }, "downloads": -1, "filename": "mp_event_loop-1.5.0.tar.gz", "has_sig": false, "md5_digest": "3c1b7b5962a5b2ca5beeea9518b26c67", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23976, "upload_time": "2018-11-02T12:23:51", "url": "https://files.pythonhosted.org/packages/29/ed/1794551fd6a4161aa7bdb8a273d637ebd98a4ebea634b3af3b08ab41c3d0/mp_event_loop-1.5.0.tar.gz" } ], "1.5.1": [ { "comment_text": "", "digests": { "md5": "bed441c5861f2308736ff910182a38c7", "sha256": "8c26f2ce123a7d8c9db1c9c94302918cb403ed79ac2140a769bfa9c16284751a" }, "downloads": -1, "filename": "mp_event_loop-1.5.1.tar.gz", "has_sig": false, "md5_digest": "bed441c5861f2308736ff910182a38c7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23993, "upload_time": "2018-11-06T14:48:57", "url": "https://files.pythonhosted.org/packages/32/a8/ed12b91c52d2a9634d98a682cba7c36ceafd88f03dc74fd37f779b6a7603/mp_event_loop-1.5.1.tar.gz" } ], "1.5.2": [ { "comment_text": "", "digests": { "md5": "23aca343c57e31e22139068c16487d17", "sha256": "f34a161869f21485484a799d1ab4791ecd59aa74003b8a459800e619b00316f9" }, "downloads": -1, "filename": "mp_event_loop-1.5.2.tar.gz", "has_sig": false, "md5_digest": "23aca343c57e31e22139068c16487d17", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24352, "upload_time": "2018-11-06T17:24:50", "url": "https://files.pythonhosted.org/packages/b7/f8/321125f22485c6b4751fe6599cbd9f84b6f7a17d93e3c30d42872e7479ee/mp_event_loop-1.5.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "23aca343c57e31e22139068c16487d17", "sha256": "f34a161869f21485484a799d1ab4791ecd59aa74003b8a459800e619b00316f9" }, "downloads": -1, "filename": "mp_event_loop-1.5.2.tar.gz", "has_sig": false, "md5_digest": "23aca343c57e31e22139068c16487d17", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24352, "upload_time": "2018-11-06T17:24:50", "url": "https://files.pythonhosted.org/packages/b7/f8/321125f22485c6b4751fe6599cbd9f84b6f7a17d93e3c30d42872e7479ee/mp_event_loop-1.5.2.tar.gz" } ] }