{ "info": { "author": "Donald Mellenbruch", "author_email": "dmellenbruch@outlook.com", "bugtrack_url": null, "classifiers": [], "description": "``ezpq``: an easy parallel queueing system.\n===========================================\n\n Read this on `GitHub `__ or `my\n site `__.\n\n- `How to get it <#how-to-get-it>`__\n- `Overview <#overview>`__\n- `Features <#features>`__\n- `Quickstart <#quickstart>`__\n- `ezpq.Queue <#ezpq.queue>`__\n- `ezpq.Job <#ezpq.job>`__\n\n - `put <#put>`__\n - `size <#size>`__\n - `wait <#wait>`__\n - `get <#get>`__\n - `collect <#collect>`__\n - `map <#map>`__\n - `starmap <#starmap>`__\n - `startmapkw <#startmapkw>`__\n - `dispose <#dispose>`__\n\n- `Synchronous Lanes <#synchronous-lanes>`__\n\n - `Lane Error Handling <#lane-error-handling>`__\n\n- `ezpq.Plot <#ezpq.plot>`__\n- `More Examples <#more-examples>`__\n\nHow to get it\n-------------\n\nInstall from `PyPI `__ with:\n\n.. code:: python\n\n pip install ezpq\n\nOptional packages:\n\n.. code:: python\n\n pip install pandas # required for plots\n pip install plotnine # required for plots\n pip install tqdm # required for progress bars\n\nOverview\n--------\n\n``ezpq`` implements a parallel queueing system consisting of:\n\n1. a priority \u201cwaiting\u201d queue in.\n2. a lookup table of \u201cworking\u201d jobs.\n3. a priority \u201ccompleted\u201d queue out.\n\nThe queueing system uses ``multiprocessing.Process`` by default and can\nalso run jobs with ``threading.Thread``.\n\n|image0|\n\nFeatures\n--------\n\n- Simple interface; pure Python.\n- No required dependencies outside of standard library.\n- Optional integration with ```tqdm`` `__\n progress bars.\n- Compatible with Python 2 & 3.\n- Cross platform with MacOS, Linux, and Windows.\n- Data remains in-memory.\n- Priority Queueing, both in and out and within lanes.\n- Synchronous lanes allow dependent jobs to execute in the desired\n order.\n- Easily switch from processes to threads.\n- Automatic handling of output.\n- Rich job details, easily viewed as pandas dataframe.\n- Built-in logging to CSV.\n- Customizable visualizations of queue operations.\n\nQuickstart\n----------\n\nSuppose you wanted to speed up the following code, which runs 60\noperations that take anywhere from 0s to 2s. With an average job time of\n~1s, this operation should take ~60s.\n\n.. code:: python\n\n import time\n import random\n def random_sleep(x):\n random.seed(x)\n n = random.uniform(0.5, 1.5)\n time.sleep(n)\n return n\n\n.. code:: python\n\n start = time.time()\n output = [random_sleep(x) for x in range(60)]\n end = time.time()\n print('> Runtime: ' + str(end - start))\n\n::\n\n ## '> Runtime: 58.932034969329834'\n\nHere is the function ran in parallel with an ``ezpq`` Queue of 6\nworkers. Thus, the runtime of the above operation will be reduced from\n~60s to ~10s.\n\n.. code:: python\n\n import time\n import random\n import ezpq\n start = time.time()\n with ezpq.Queue(6) as Q:\n output = Q.map(random_sleep, range(60))\n end = time.time()\n print('> Runtime: ' + str(end - start))\n\nHere is the same scenario, using the ``@ezpq.Queue`` decorator.\n\n.. code:: python\n\n @ezpq.Queue(6)\n def random_sleep(x):\n random.seed(x)\n n = random.uniform(0.5, 1.5)\n time.sleep(n)\n return n\n output = random_sleep(iterable=range(60))\n\nWhile ``map()`` and the decorator are useful for quick-n-simple\nparallization, the essential functions of an ``ezpq`` Queue include\n``put()``, ``wait()``, and ``get()`` (or ``collect()``).\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n for x in range(60):\n Q.put(random_sleep, args=x)\n Q.wait()\n output = Q.collect()\n\nThe output is a list of dicts containing verbose information about each\njob, along with its output, and exit code.\n\n.. code:: python\n\n print( output[0] )\n\n::\n\n ## {'args': [0],\n ## 'callback': None,\n ## 'cancelled': False,\n ## 'ended': datetime.datetime(2019, 3, 13, 0, 48, 52, 811248),\n ## 'exception': None,\n ## 'exitcode': 0,\n ## 'function': 'random_sleep',\n ## 'id': 1,\n ## 'kwargs': None,\n ## 'lane': None,\n ## 'name': 1,\n ## 'output': 1.3444218515250481,\n ## 'priority': 100,\n ## 'processed': datetime.datetime(2019, 3, 13, 0, 48, 52, 867387),\n ## 'qid': '13318d36',\n ## 'runtime': 1.3500409126281738,\n ## 'started': datetime.datetime(2019, 3, 13, 0, 48, 51, 461207),\n ## 'submitted': datetime.datetime(2019, 3, 13, 0, 48, 51, 357405),\n ## 'timeout': 0}\n\nEasily convert output to a ``pandas`` dataframe:\n\n.. code:: python\n\n import pandas as pd\n df = pd.DataFrame(output)\n print( df.head()[['id', 'output', 'runtime', 'exitcode']] )\n\n::\n\n ## id output runtime exitcode\n ## 0 1 1.344422 1.350041 0\n ## 1 2 0.634364 0.638938 0\n ## 2 3 1.456034 1.459830 0\n ## 3 4 0.737965 0.741742 0\n ## 4 5 0.736048 0.739848 0\n\nUse ``ezpq.Plot`` to generate a Gannt chart of the job timings.\n\n.. code:: python\n\n plt = ezpq.Plot(output).build(show_legend=False)\n plt.save('docs/imgs/quickstart.png')\n\n|image1|\n\nezpq.Queue\n----------\n\nThe ``Queue`` class implements the queueing system, which is itself a\n3-part system composed of the:\n\n1. waiting queue\n2. working table\n3. completed queue\n\n\n \n\n::\n\n ## Help on function __init__ in module ezpq.Queue:\n ## \n ## __init__(self, n_workers=8, max_size=0, job_runner=, auto_remove=False, auto_start=True, auto_stop=False, callback=None, log_file=None, poll=0.1, show_progress=False, qid=None)\n ## Implements a parallel queueing system.\n ## \n ## Args:\n ## n_workers: the max number of concurrent jobs.\n ## - Accepts: int\n ## - Default: cpu_count()\n ## max_size: when > 0, will throw an exception the number of enqueued jobs exceeds this value. Otherwise, no limit.\n ## - Accepts: int\n ## - Default: 0 (unlimited)\n ## job_runner: the class to use to invoke new jobs.\n ## - Accepts: multiprocessing.Process, threading.Thread\n ## - Default: multiprocessing.Process\n ## auto_remove: controls whether jobs are discarded of after completion.\n ## - Accepts: bool\n ## - Default: False\n ## auto_start: controls whether the queue system \"pulse\" is started upon instantiation (default), or manually.\n ## - Accepts: bool\n ## - Default: True\n ## auto_stop: controls whether the queue system \"pulse\" stops itself after all jobs are complete.\n ## - Accepts: bool\n ## - Default: False\n ## callback: optional function to execute synchronously immediately after a job completes.\n ## - Accepts: function object\n ## - Default: None\n ## log_file: if file path is specified, job data is written to this path in CSV format.\n ## - Accepts: str\n ## - Default: None\n ## poll: controls the pulse frequency; the amount of time slept between operations.\n ## - Accepts: float\n ## - Default: 0.1\n ## \n ## Returns:\n ## ezpq.Queue object.\n ## \n ## None\n\nezpq.Job\n--------\n\nA ``ezpq`` job defines the function to run. It is passed to an ``ezpq``\nqueue with a call to ``submit()``.\n\n::\n\n ## Help on function __init__ in module ezpq.Job:\n ## \n ## __init__(self, function, args=None, kwargs=None, name=None, priority=100, lane=None, timeout=0, suppress_errors=False, stop_on_lane_error=False)\n ## Defines what to run within a `ezpq.Queue`, and how to run it.\n ## \n ## Args:\n ## function: the function to run.\n ## - Accepts: function object\n ## args: optional positional arguments to pass to the function.\n ## - Accepts: list, tuple\n ## - Default: None\n ## kwargs: optional keyword arguments to pass to the function.\n ## - Accepts: dict\n ## - Default: None\n ## name: optional name to give to the job. Does not have to be unique.\n ## - Accepts: str\n ## - Default: None; assumes same name as job id.\n ## priority: priority value to assign. Lower values get processed sooner.\n ## - Accepts: int\n ## - Default: 100\n ## lane: a sequential lane to place the job in. if it does not already exist, it will be created.\n ## - Accepts: int, str; any hashable object\n ## - Default: None; no lanes.\n ## timeout: When > 0, if this value (in seconds) is exceeded, the job is terminated. Otherwise, no limit enforced.\n ## - Accepts: float\n ## - Default: 0 (unlimited)\n ## \n ## Returns:\n ## ezpq.Job object\n ## \n ## None\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n for x in range(60):\n priority = x % 2 # give even numbers higher priority.\n job = ezpq.Job(random_sleep, args=x, priority=priority)\n Q.submit(job)\n Q.wait()\n output = Q.collect()\n\n|image2|\n\nput\n~~~\n\nThe ``put`` method creates a job and submits it to an ``ezpq`` queue.\nAll of its arguments are passed to ``ezpq.Job()``.\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n for x in range(60):\n Q.put(random_sleep, args=x)\n Q.wait()\n output = Q.collect()\n\nsize\n~~~~\n\n``size()`` returns a count of all items across all three queue\ncomponents. It accepts three boolean parameters, ``waiting``,\n``working``, and ``completed``. If all of these are ``False`` (default),\nall jobs are counted. If any combination of these is ``True``, only the\ncorresponding queue(s) will be counted. For example:\n\n.. code:: python\n\n def print_sizes(Q):\n msg = 'Total: {0}; Waiting: {1}; Working: {2}; Completed: {3}'.format(\n Q.size(),\n Q.size(waiting=True),\n Q.size(working=True),\n Q.size(completed=True)\n )\n print(msg)\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n # enqueue jobs\n for x in range(60):\n Q.put(random_sleep, x)\n # repeatedly print sizes until complete.\n while Q.size(waiting=True, working=True):\n print_sizes(Q)\n time.sleep(1)\n print_sizes(Q)\n\n::\n\n ## 'Total: 60; Waiting: 60; Working: 0; Completed: 0'\n ## 'Total: 60; Waiting: 51; Working: 6; Completed: 3'\n ## 'Total: 60; Waiting: 46; Working: 6; Completed: 8'\n ## 'Total: 60; Waiting: 39; Working: 6; Completed: 15'\n ## 'Total: 60; Waiting: 34; Working: 6; Completed: 20'\n ## 'Total: 60; Waiting: 31; Working: 6; Completed: 23'\n ## 'Total: 60; Waiting: 24; Working: 6; Completed: 30'\n ## 'Total: 60; Waiting: 17; Working: 6; Completed: 37'\n ## 'Total: 60; Waiting: 11; Working: 6; Completed: 43'\n ## 'Total: 60; Waiting: 6; Working: 6; Completed: 48'\n ## 'Total: 60; Waiting: 0; Working: 5; Completed: 55'\n ## 'Total: 60; Waiting: 0; Working: 1; Completed: 59'\n ## 'Total: 60; Waiting: 0; Working: 0; Completed: 60'\n\nwait\n~~~~\n\nThe ``wait()`` method will block execution until all jobs complete. It\nalso accepts a ``timeout`` parameter, given in seconds. The return value\nis the count of jobs that did not complete. Thus, a return value greater\nthan 0 indicates the timeout was exceeded. The parameter ``poll`` can be\nused to adjust how frequently (in seconds) the operation checks for\ncompleted jobs.\n\nNew in v0.2.0, include ``show_progress=True`` to show a progress bar\nwhile waiting. This is equivalent to a call to ``waitpb()``.\n\n|image3|\n\nget\n~~~\n\n``get()`` retrieves and deletes (\u201cpop\u201d) the highest priority job from\nthe completed queue, if one is available. If the completed queue is\nempty, ``get()`` returns ``None``. However, ``get()`` will wait for a\ncompleted job if ``wait``, ``poll``, or ``timeout`` are specified. If\nthe timeout is exceeded, ``None`` is returned.\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n n_inputs = 60\n output = [None] * n_inputs\n # enqueue jobs\n for x in range(n_inputs):\n Q.put(random_sleep, args=x)\n\n # repeatedly `get()` until queue is empty.\n for i in range(n_inputs):\n output[i] = Q.get(wait=True)\n\ncollect\n~~~~~~~\n\n``collect()`` is similar to ``get()``, but it will return a list of\n*all* completed jobs and clear the completed queue. It does not support\nthe ``poll`` or ``timeout`` parameters, but you can call ``wait()``\nbefore ``collect()`` if desired.\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n # enqueue jobs\n for x in range(60):\n Q.put(random_sleep, x)\n # wait and collect all jobs\n print('Queue size before: {0}'.format(Q.size()))\n Q.wait()\n output = Q.collect()\n print('Queue size after: {0}'.format(Q.size()))\n print('Output size: {0}'.format(len(output)))\n\n::\n\n ## 'Queue size before: 60'\n ## 'Queue size after: 0'\n ## 'Output size: 60'\n\nmap\n~~~\n\n``map`` encapsulates the logic of ``put``, ``wait``, and ``collect`` in\none call. Include ``show_progress=True`` to get output ``tqdm`` progress\nbar.\n\n|image4|\n\nstarmap\n~~~~~~~\n\n``starmap`` is similar to ``map``, but operates on a list of lists, with\neach nested list being unpacked as arguments to the function.\n\n.. code:: python\n\n def my_pow(x, k):\n return '{}^{} = {}'.format(x, k, x**k)\n # list of lists to iterate over.\n args_list = [[x, x%4] # (x, k)\n for x in range(100)]\n # starmap\n with ezpq.Queue(10) as Q:\n output = Q.starmap(my_pow, iterable=args_list)\n\n [x['output'] for x in output[:10]]\n\nstartmapkw\n~~~~~~~~~~\n\nSame as ``starmap``, but operations on a list of *dicts* to be expanded\nas kwargs to the function.\n\n.. code:: python\n\n def my_pow(x, k):\n return '{}^{} = {}'.format(x, k, x**k)\n # list of dicts to iterate over.\n kwargs_list = [{ 'x':x, 'k':x%4 } # (x, k)\n for x in range(100)]\n # starmapkw\n with ezpq.Queue(10) as Q:\n output = Q.starmapkw(my_pow, iterable=kwargs_list)\n\n [x['output'] for x in output[:10]]\n\ndispose\n~~~~~~~\n\nThe queueing operations performed by ``ezpq.Queue`` are performed on a\nperiodic basis. By default, the ``poll`` parameter for a Queue is\n``0.1`` seconds. This \u201cpulse\u201d thread will continue firing until the\nQueue is disposed of.\n\nIn the previous examples, use of the context manager\n(``with ezpq.Queue() as Q:``) results in automatic disposal. If not\nusing the context manager (or decorator), clean up after yourself with\n``dispose()``.\n\nSynchronous Lanes\n-----------------\n\nWhen you have jobs that are dependent upon another, you can use \u201clanes\u201d\nto execute them in sequence. All that is required is an arbitrary lane\nname/id passed to the ``lane`` parameter of ``put``. Empty lanes are\nautomatically removed.\n\n|image5|\n\nIn the above graphic, notice how same-colored bars never overlap. These\nbars represent jobs that are in the same lane, which executed\nsynchronously.\n\nLane Error Handling\n~~~~~~~~~~~~~~~~~~~\n\nYou may want to short-circuit a synchronous lane if a job in the lane\nfails. You can do this by specifying ``stop_on_lane_error=True`` when\nputting a job in the queue. If specified and the preceding job has a\nnon-zero exit code, this job will not be run.\n\n.. code:: python\n\n def reciprocal(x):\n time.sleep(0.1) # slow things down\n return 1/x # will throw DivideByZero exception\n\n.. code:: python\n\n import random\n with ezpq.Queue(6) as Q:\n for i in range(100):\n Q.put(reciprocal, random.randint(0, 10), lane=i%5, suppress_errors=True, stop_on_lane_error=True)\n Q.wait()\n output = Q.collect()\n plt = ezpq.Plot(output).build(facet_by='lane', color_by='exitcode', color_pal=['red', 'blue'])\n plt.save('docs/imgs/lane_error.png')\n\n|image6|\n\nezpq.Plot\n---------\n\nThe ``Plot`` class is used to visualize the wait, start, and end times\nfor each job that entered the queueing system. The class is initialized\nwith a list of dicts; exactly what is returned from a call to\n``collect()`` or ``map()``.\n\nArguments given to ``build()`` control various aspects of the plot, from\ncoloring, to faceting,\n\n::\n\n ## Help on function build in module ezpq.Plot:\n ## \n ## build(self, color_by='qid', facet_by='qid', facet_scale='fixed', show_legend=True, bar_width=1, title=None, color_pal=None, theme='bw')\n ## Produces a plot based on the data and options provided to a `ezpq.Plot()` object.\n ## \n ## Args:\n ## color_by: controls the column to use for coloring the bars.\n ## - Accepts: one of 'qid', 'priority', 'lane', 'cancelled', 'exitcode', 'name', 'output'\n ## - Default: 'qid'\n ## facet_by: controls the column to use for facetting the plot.\n ## - Accepts: one of 'qid', 'priority', 'lane', 'cancelled', 'exitcode', 'name', 'output'\n ## - Default: 'qid'\n ## facet_scale: controls the scale of the x/y axis across facets.\n ## - Accepts: one of 'fixed', 'free', 'free_x', 'free_y'\n ## - Default: 'fixed'\n ## show_legend: controls whether the legend is drawn.\n ## - Accepts: bool\n ## - Default: True\n ## bar_width: controls the bar width\n ## - Accepts: float\n ## - Default: 1\n ## title: optional title to be drawn above the plot.\n ## - Accepts: str, None\n ## - Default: None\n ## theme:\n ## - Accepts: 'bw', 'classic', 'gray', 'grey', 'seaborn', '538', 'dark', 'matplotlib', 'minimal', 'xkcd', 'light'\n ## - Default: 'bw'\n ## Returns:\n ## The plot produced from plotnine.ggplot().\n ## \n ## None\n\n.. code:: python\n\n with ezpq.Queue(6) as Q:\n for x in range(60):\n lane = x % 5\n Q.put(random_sleep, x, timeout=1, lane=lane)\n Q.wait()\n output = Q.collect()\n\n.. code:: python\n\n plt = ezpq.Plot(output).build(facet_by='lane', show_legend=False)\n plt.save('docs/imgs/lanes2.png')\n\n|image7|\n\nEach horizontal bar represents an independent job id. The start of the\ngray bar indicates when the job entered the queuing system. The start of\nthe colored bar indicates when the job started running, and when it\nended. The gray bar that follows (if any) reflects how long it took for\nthe queue operations to recognize the finished job, join the job data\nwith its output, remove it from the working table, and place it in the\ncompleted queue.\n\nMore Examples\n-------------\n\nMany more examples can be found in\n`docs/examples.ipynb `__.\n\n.. |image0| image:: docs/imgs/ezpq.png\n.. |image1| image:: docs/imgs/quickstart.png\n.. |image2| image:: docs/imgs/submit.png\n.. |image3| image:: docs/imgs/tqdm.gif\n.. |image4| image:: docs/imgs/tqdm_map.gif\n.. |image5| image:: docs/imgs/lanes.gif\n.. |image6| image:: docs/imgs/lane_error.png\n.. |image7| image:: docs/imgs/lanes2.png\n\n\n\n", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://www.github.com/dm3ll3n/ezpq", "keywords": "Parallel,Queue", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "ezpq", "package_url": "https://pypi.org/project/ezpq/", "platform": "", "project_url": "https://pypi.org/project/ezpq/", "project_urls": { "Homepage": "https://www.github.com/dm3ll3n/ezpq" }, "release_url": "https://pypi.org/project/ezpq/0.2.2/", "requires_dist": [ "numpy ; extra == 'plot'", "pandas ; extra == 'plot'", "matplotlib ; extra == 'plot'", "plotnine ; extra == 'plot'" ], "requires_python": "", "summary": "an easy parallel queueing system", "version": "0.2.2" }, "last_serial": 4933239, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "5ddb14b822e93578639bbcb0a6065bdb", "sha256": "2f8deb951c931f2e63b28c4026e7a0df8c017b8635d750826982b2982a645445" }, "downloads": -1, "filename": "ezpq-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "5ddb14b822e93578639bbcb0a6065bdb", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 14807, "upload_time": "2018-12-14T19:13:26", "url": "https://files.pythonhosted.org/packages/e1/cf/53015301e32c71dd894b12c2f9796d45e93eb189b006904843c02686c0f2/ezpq-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3b119e180dd96678f74c22af73d2a60f", "sha256": "08030106d7b1164539e986ad520e453416c3d485e17f763c94c184037e395b2f" }, "downloads": -1, "filename": "ezpq-0.1.0.tar.gz", "has_sig": false, "md5_digest": "3b119e180dd96678f74c22af73d2a60f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20022, "upload_time": "2018-12-14T19:13:28", "url": "https://files.pythonhosted.org/packages/29/ca/a3bd2d18b53f1308a4bd66befb0483d1cb38de08e9c02eaf5134cddf69c2/ezpq-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "1492bc4b919b51caeda7331dae077a6f", "sha256": "59c4707aa9b21a27762d2ca26b8b477c6b1f012e682b0ea576bf89445c531b42" }, "downloads": -1, "filename": "ezpq-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "1492bc4b919b51caeda7331dae077a6f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17013, "upload_time": "2019-01-29T01:07:33", "url": "https://files.pythonhosted.org/packages/61/1f/b5932dbc6b90d4f0f084da7145c09807f2b880b423628a214436d68c94f7/ezpq-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ea5678a8268aeef9f85fa012cd269a88", "sha256": "fc662431549ba7ba0a240253284a0e7b02f19a52425a42e3460feac9c5faac16" }, "downloads": -1, "filename": "ezpq-0.2.0.tar.gz", "has_sig": false, "md5_digest": "ea5678a8268aeef9f85fa012cd269a88", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16475, "upload_time": "2019-01-29T01:07:34", "url": "https://files.pythonhosted.org/packages/c9/11/526ce5c7ecf66814c988d45b3d29b41e1bd252abf65f39730283c353decc/ezpq-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "d2863b72dd43de32695585574324d24a", "sha256": "77c70924df924f350acc43f9e63431245536ea0074630238c8fddd736996967d" }, "downloads": -1, "filename": "ezpq-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "d2863b72dd43de32695585574324d24a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17993, "upload_time": "2019-02-19T02:46:15", "url": "https://files.pythonhosted.org/packages/82/51/6f66ae2eaf0ee092a235911c1a794d19718bf79fded9745ef0158772a442/ezpq-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "7dbe23225c417cc7e8153b479ca95158", "sha256": "6e0afc9ae93ad244db75b9e70a6da83191908ced2702f625bc1014c424853b25" }, "downloads": -1, "filename": "ezpq-0.2.1.tar.gz", "has_sig": false, "md5_digest": "7dbe23225c417cc7e8153b479ca95158", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18066, "upload_time": "2019-02-19T02:46:17", "url": "https://files.pythonhosted.org/packages/2b/b7/9ab5a51b05994273d63eb9fc6ee649aa2a56a349f0aec692426bf38f99ba/ezpq-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "53f4edfac87003315e07ed1b4d6ce2ed", "sha256": "54e6787abb0ef3afd8c640902646404c3f60ee53586ec9b10e7c69d6e77ffcd2" }, "downloads": -1, "filename": "ezpq-0.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "53f4edfac87003315e07ed1b4d6ce2ed", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 18360, "upload_time": "2019-03-13T06:11:18", "url": "https://files.pythonhosted.org/packages/16/29/f87c87278a1db8edf092f4e5338db63243cf26258bc1f0e612bf915b831f/ezpq-0.2.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "393919659d8260969a56f07a2e7a778f", "sha256": "2d67d37f7b2e1024285ad6850551eab80f0b68ec335e3b160c856f23c8f9edca" }, "downloads": -1, "filename": "ezpq-0.2.2.tar.gz", "has_sig": false, "md5_digest": "393919659d8260969a56f07a2e7a778f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24069, "upload_time": "2019-03-13T06:11:20", "url": "https://files.pythonhosted.org/packages/7f/b6/384f2755bf0bf55b057c01e0708362003fd55fe50754f24f9879677f7046/ezpq-0.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "53f4edfac87003315e07ed1b4d6ce2ed", "sha256": "54e6787abb0ef3afd8c640902646404c3f60ee53586ec9b10e7c69d6e77ffcd2" }, "downloads": -1, "filename": "ezpq-0.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "53f4edfac87003315e07ed1b4d6ce2ed", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 18360, "upload_time": "2019-03-13T06:11:18", "url": "https://files.pythonhosted.org/packages/16/29/f87c87278a1db8edf092f4e5338db63243cf26258bc1f0e612bf915b831f/ezpq-0.2.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "393919659d8260969a56f07a2e7a778f", "sha256": "2d67d37f7b2e1024285ad6850551eab80f0b68ec335e3b160c856f23c8f9edca" }, "downloads": -1, "filename": "ezpq-0.2.2.tar.gz", "has_sig": false, "md5_digest": "393919659d8260969a56f07a2e7a778f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24069, "upload_time": "2019-03-13T06:11:20", "url": "https://files.pythonhosted.org/packages/7f/b6/384f2755bf0bf55b057c01e0708362003fd55fe50754f24f9879677f7046/ezpq-0.2.2.tar.gz" } ] }