{ "info": { "author": "Luca Sbardella", "author_email": "luca@quantmind.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Utilities" ], "description": "|pulsar-queue|\n\n:Badges: |license| |pyversions| |status| |pypiversion|\n:Master CI: |master-build| |coverage-master|\n:Downloads: http://pypi.python.org/pypi/pulsar-queue\n:Source: https://github.com/quantmind/pulsar-queue\n:Mailing list: `google user group`_\n:Design by: `Quantmind`_ and `Luca Sbardella`_\n:Platforms: Linux, OSX, Windows. Python 3.5 and above\n:Keywords: server, asynchronous, concurrency, actor, process, queue, tasks, redis\n\n\n.. |pypiversion| image:: https://badge.fury.io/py/pulsar-queue.svg\n :target: https://pypi.python.org/pypi/pulsar-queue\n.. |pyversions| image:: https://img.shields.io/pypi/pyversions/pulsar-queue.svg\n :target: https://pypi.python.org/pypi/pulsar-queue\n.. |license| image:: https://img.shields.io/pypi/l/pulsar-queue.svg\n :target: https://pypi.python.org/pypi/pulsar-queue\n.. |status| image:: https://img.shields.io/pypi/status/pulsar-queue.svg\n :target: https://pypi.python.org/pypi/pulsar-queue\n.. |downloads| image:: https://img.shields.io/pypi/dd/pulsar-queue.svg\n :target: https://pypi.python.org/pypi/pulsar-queue\n.. |master-build| image:: https://img.shields.io/travis/quantmind/pulsar-queue/master.svg\n :target: https://travis-ci.org/quantmind/pulsar-queue\n.. |dev-build| image:: https://img.shields.io/travis/quantmind/pulsar-queue/dev.svg\n :target: https://travis-ci.org/quantmind/pulsar-queue?branch=dev\n.. |coverage-master| image:: https://coveralls.io/repos/github/quantmind/pulsar-queue/badge.svg?branch=master\n :target: https://coveralls.io/github/quantmind/pulsar-queue?branch=master\n.. |coverage-dev| image:: https://coveralls.io/repos/github/quantmind/pulsar-queue/badge.svg?branch=dev\n :target: https://coveralls.io/github/quantmind/pulsar-queue?branch=dev\n\n\nAsynchronous server for consuming asynchronous IO tasks, green IO tasks,\nblocking IO tasks and long running CPU bound tasks.\n\n* Fully configurable\n* Consumers poll tasks from distributed message brokers (redis broker implemented)\n* Publish/subscribe for real-time event and logging (redis pub/sub backend)\n* Can schedule tasks when run as a scheduler (``--schedule-periodic`` flag)\n* Build on top of pulsar_ and asyncio_\n\n\nTL;DR\n========\n\nClone the repository::\n\n git clone git@github.com:quantmind/pulsar-queue.git\n\n\nMove to the ``tests/example`` directory and run the server::\n\n python manage.py\n\n\n\n.. contents:: **CONTENTS**\n\n\nFour steps tutorial\n========================\n\n1 - Create a script\n----------------------\n\nA simple python file which runs your application:\n\n.. code::\n\n vim manage.py\n\n\n.. code:: python\n\n from pq.api import PusarQueue\n\n\n task_paths = ['sampletasks.*', 'pq.jobs']\n\n\n def app():\n return PusarQueue(config=__file__)\n\n if __name__ == '__main__':\n app().start()\n\n\n2 - Implement Jobs\n---------------------\n\nCreate the modules where Jobs_ are implemented.\nIt can be a directory containing several submodules.\n\n.. code::\n\n mkdir sampletasks\n cd sampletasks\n vim mytasks.py\n\n.. code:: python\n\n import asyncio\n import time\n\n from pq import api\n\n\n @api.job()\n def addition(self, a=0, b=0):\n return a + b\n\n\n @api.job()\n async def asynchronous(self, lag=1):\n start = time.time()\n await asyncio.sleep(lag)\n return time.time() - start\n\n\n3 - Run the server\n---------------------\n\nRun the server with two task consumers (pulsar actors).\n\n**NOTE**: Make sure you have Redis server up and running before you start the server.\n\n.. code::\n\n python manage.py -w 2\n\n4 - Queue tasks\n---------------------\n\nLaunch a python shell and play with the api\n\n.. code:: python\n\n >>> from manage import app\n >>> api = app().api()\n >>> task = api.tasks.queue('addition', a=4, b=6)\n >>> task\n \n >>> task = task.wait()\n task.addition\n >>> task.result\n 10\n >>> task.status_string\n 'SUCCESS'\n\nYou can also queue tasks with a ``delay``\n\n.. code:: python\n\n >>> task = api.tasks.queue('addition', a=4, b=6, callback=False, delay=2).wait()\n >>> task.status_string\n 'QUEUED'\n >>> task.time_queued # timestamp\n >>> task = task.done_callback.wait()\n >>> task.status_string\n 'SUCCESS'\n >>> task.time_started - task.time_queued\n 2.00\n\n**NOTE**: The ``wait`` method in a task future can only be used on the shell\nor when the event loop is not running. In all other cases one should ``await``\nfor the task future in a coroutine.\n\nAPI\n=============\n\nThe producer API is obtained from the Task application ``api`` method:\n\n.. code:: python\n\n from pq.api import PusarQueue\n\n api = PusarQueue(...).api()\n\n\nAPI methods\n---------------\n\n*api*.start()\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nStart listening to events. This method return a coroutine which resolve in the api:\n\n.. code:: python\n\n api = await api.start()\n\nThe start method is used when the api is used by application to queue messages/tasks\nand listen for events published by distributed consumers.\n\n*api*.on_events(*message_type*, *event_re*, *callback*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAdd a callback invoked every time an event matching the regular expression ``event_re``\noccurs on the ``message_type`` channel. The *callback* has the following signature:\n\n.. code:: python\n\n def event_callback(channel, event, message):\n # event is string, the event matched\n # message is of type message_type\n\nIf the event is a task event (see events_) the message is a Task_ object.\n\nThis method is useful when creating applications which needs to respond to the\nqueue server events in real time::\n\n api.on_events('task', 'queued', callback)\n api.on_events('task', 'started', callback)\n api.on_events('task', 'done', callback)\n\n\n*api*.remove_event_callback(*message_type*, *event_re*, *callback*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nRemove a previously added event callback. This method is safe.\n\n*api*.queue(*message*, *callback=True*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nQueue a message in the message queue, equivalent to:\n\n.. code:: python\n\n api.broker.queue(message, callback)\n\nThis method returns a ``MessageFuture``, a subclass of asyncio Future_ which\nresolves in a ``message`` object.\nIf ``callback`` is True (default) the Future is resolved once the message\nis delivered (out of the queue), otherwise is is resolved once the message\nis queued (entered the queue).\n\n*api*.execute(*message*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nExecute a message without queueing. This is only supported by messages with\na message consumer which execute them (the ``tasks`` consumer for example).\nIf *message* is a Task_, this method is equivalent to:\n\n.. code:: python\n\n api.tasks.execute(task)\n\nThis method returns a ``MessageFuture``, a subclass of asyncio Future_ which\nresolve in a ``message`` object.\n\n*api*.consumers\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nList of consumers registered with the api.\n\nTasks API\n-----------------\n\nThe tasks producer is obtained vua the ``tasks`` property from the producer API instance\n\n.. code:: python\n\n tasks = api.tasks\n\nThe following methods are available for the tasks producer:\n\n\n*tasks*.queue(*jobname*, *\\*\\*kwargs*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nQueue a task and return a **TaskFuture** which is resolved once the task has finished.\nIt is possible to obtain a task future resolved when the task has been queued, rather than finished, by passing the **callback=False** parameter:\n\n.. code:: python\n\n task = await tasks.queue(..., callback=False)\n task.status_string # QUEUED\n\nThe ``kwargs`` parameters are used as input parameters for the Job_ callable with the exception of:\n\n* ``callback``: discussed above\n* ``delay``: delay execution by a given number of seconds\n* ``queue``: overrides the Job_ [default_queue](#job-default-queue)\n* [timeout](#job-timeout)\n* ``meta_params``: dictionary of parameters used by the Job_ callable to override default values of:\n * [max_retries](#job-max-retries)\n * [retry_delay](#job-retry-delay)\n * [max_concurrency](#job-max-concurrency)\n\n*tasks*.queue_local(*jobname*, *\\*\\*kwargs*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nQueue a job in the local task queue. The local task queue is processed by the same server instance. It is equivalent to execute:\n\n.. code:: python\n\n task = await tasks.queue(..., queue=tasks.node_name)\n task.queue # tasks.node_name\n\n\n*tasks*.execute(*jobname*, *\\*args*, *\\*\\*kwargs*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nExecute a task immediately, it does not put the task in the task queue.\nThis method is useful for debugging and testing. It is equivalent to execute:\n\n.. code:: python\n\n task = await tasks.queue(..., queue=False)\n task.queue # None\n task.status_string # SUCCESS\n\n\n*tasks*.queues()\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nReturn the list of queue names the backend is subscribed. This list is not empty when the backend is a task consumer.\n\n*tasks*.job_list(*jobname=None*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nReturns a list of ``job_name``, ``job_description`` tuples. The ``job_name`` is a string which must be used as the **jobname** parameter when executing or queing tasks. The ``job_description`` is a dictionary containing metadata and documentation for the job. Example:\n\n.. code:: python\n\n jobs = dict(tasks.job_lits())\n jobs['execute.python']\n # {\n # 'type': 'regular',\n # 'concurrency': 'asyncio',\n # 'doc_syntax': 'markdown',\n # 'doc': 'Execute arbitrary python code on a subprocess ... '\n # }\n\n\nThe Job class\n-----------------\n\nThe **Job** class is how task factories are implemented and added to the\ntasks backend registry. When writing a new **Job** one can either subclass:\n\n.. code:: python\n\n import asyncio\n\n class AsyncSleep(api.Job):\n\n async def __call__(self, lag=1):\n await asyncio.sleep(lag)\n\n\nor use the less verbose **job** decorator:\n\n.. code:: python\n\n @api.job()\n async def asyncsleep(self, lag=1):\n await asyncio.sleep(lag)\n\n\nIn either cases the ``self`` parameter is an instance of a **Job** class and\nit has the following useful attributes and methods:\n\n*job*.backend\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe tasks backend that is processing this Task_ run\n\n*job*.default_queue\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe default queue name where tasks for this job are queued. By default it is ``None``\nin which case, if a ``queue`` is not given when queueing a task, the first queue\nfrom the `queues <#tasks_queues>`_ list taken.\n\n*job*.http\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBest possible HTTP session handler for the job concurrency mode.\n\n*job*.logger\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPython logging handler for this job. The name of this handler\nis ``.``.\n\n*job*.max_retries\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOptional positive integer which specify the maximum number of retries when a\ntask fails or is revoked. If not available failing tasks are not re-queued.\nIt can be specified as a class attribute or during initialisation from the task\nmeta parameters.\n\n*job*.retry_delay\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOptional positive integer which specifies the number of seconds to delay a task\nretry.\n\n*job*.name\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe name of this job. Used to queue tasks\n\n*job*.task\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe Task_ instance associated with this task run\n\n*job*.queue(*jobname*, *\\*args*, *\\*\\*kwargs*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nQueue a new job form a task run. It is equivalent to:\n\n.. code:: python\n\n meta_params = {'from_task': self.task.id}\n self.backend.tasks.queue(..., meta_params=meta_params)\n\n\n*job*.shell(*command*, *\\*\\*kwargs*)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nExecute a shell command and returns a coroutine:\n\n.. code:: python\n\n await self.shell(\"...\")\n\n\nThe Task\n-----------\n\nA task contains the metadata information of a job run and it is exchanged between task producers and task consumers via a distributed task queue.\n\n\nTask States\n-----------------\n\nA Task_ can have one of the following ``task.status``:\n\n* ``QUEUED = 6`` a task queued but not yet executed.\n* ``STARTED = 5`` a task where execution has started.\n* ``RETRY = 4`` a task is retrying calculation.\n* ``REVOKED = 3`` the task execution has been revoked (or timed-out).\n* ``FAILURE = 2`` task execution has finished with failure.\n* ``SUCCESS = 1`` task execution has finished with success.\n\n\n**FULL_RUN_STATES**\n\nThe set of states for which a Task_ has run: ``FAILURE`` and ``SUCCESS``\n\n\n**READY_STATES**\n\nThe set of states for which a Task_ has finished: ``REVOKED``, ``FAILURE`` and ``SUCCESS``\n\nEvents\n-------------\n\nThe task queue broadcast several events during task execution and internal state:\n\n* ``task_queued``: a new Task_ has been queued, the message is a task instance\n* ``task_started``: a Task_ has started to be consumed by a task consumer, it is out of the task queue\n* ``task_done``: a Task_ is done, the message is a task in a **READY_STATES**\n\n\nConfiguration\n------------------\n\nThere are several parameters you can use to twick the way the task queue works.\nIn this list the name in bold is the entry point in the config file and **cfg**\ndictionary, while, the value between brackets shows the command line entry with default\nvalue.\n\n* **concurrent_tasks** (``--concurrent-tasks 5``)\n\n The maximum number of concurrent tasks for a given worker in a task consumer server.\n\n* **data_store** (``--data-store redis://127.0.0.1:6379/7``)\n\n Data store used for publishing and subscribing to messages (redis is the\n only backend available at the moment)\n\n* **max_requests** (``--max-requests 0``)\n\n The maximum number of tasks a worker will process before restarting.\n A 0 value (the default) means no maximum number, workers will process\n all tasks forever.\n\n* **message_broker** (``--message-broker ...``)\n\n Data store used as distributed task queue. If not provided (default) the\n ``data_store`` is used instead. Redis is the\n only backend available at the moment.\n\n* **message_serializer** (``--message-serializer json``)\n\n The decoder/encoder for messages and tasks. The default is **JSON** but **Message Pack**\n is also available if msgpack_ is installed.\n\n* **schedule_periodic** (``--schedule-periodic``)\n\n When ``True``, the task application can schedule periodic Jobs_.\n Usually, only one running server is responsible for\n scheduling tasks.\n\n* **task_pool_timeout** (``--task-pool-timeout 2``)\n\n Timeout in seconds for asynchronously polling tasks from the queues. No need to change this parameter really.\n\n* **workers** (``--workers 4``)\n\n Number of workers (processes) consuming tasks.\n\n\nTasks Concurrency\n======================\n\nA task can run in one of four ``concurrency`` modes.\nIf not specified by the ``Job``, the concurrency mode is ``ASYNC_IO``.\n\nASYNC_IO\n-----------\n\nThe asynchronous IO mode is associated with tasks which return\nan asyncio Future or a coroutine. These tasks run concurrently\nin the worker event loop.\nAn example can be a Job to scrape web pages and create new tasks to process the html\n\n.. code:: python\n\n @api.job()\n async def scrape(self, url=None):\n assert url, \"url is required\"\n request = await self.http.get(url)\n html = request.text()\n task = self.queue('process.html', html=html, callback=False)\n return task.id\n\n\nTHREAD_IO\n-------------\n\nThis concurrency mode is best suited for tasks performing\n*blocking* IO operations.\nA ``THREAD_IO`` job runs its tasks in the event loop executor.\nYou can use this model for most blocking operation unless\n\n* Long running CPU bound\n* The operation does not release the GIL\n\nExample of tasks suitable for thread IO are IO operations on files.\nFor example the test suite uses this Job for testing ``THREAD_IO``\nconcurrency (check the ``tests.example.jobs.standard`` module\nfor the full code):\n\n\n.. code:: python\n\n @api.job(concurrency=api.THREAD_IO)\n def extract_docx(self, input=None, output=None):\n \"\"\"\n Extract text from a docx document\n \"\"\"\n import docx\n assert input and output, \"input and output must be given\"\n document = docx.Document(input)\n text = '\\n\\n'.join(_docx_text(document))\n with open(output, 'w') as fp:\n fp.write(text)\n return {\n 'thread': threading.get_ident(),\n 'text': len(text)\n }\n\nCPUBOUND\n------------\n\nIt assumes the task performs blocking CPU bound operations.\nJobs with this consurrency mode run their tasks on sub-processeses\nusing `asyncio subprocess`_ module.\n\nExtend\n=================\n\nIt is possible to enhance the task queue application by passing\na custom ``Manager`` during initialisation.\nFor example:\n\n.. code:: python\n\n from pq import api\n\n class Manager(api.Manager):\n\n async def store_message(self, message):\n \"\"\"This method is called when a message/task is queued,\n started and finished\n \"\"\"\n if message.type == 'task':\n # save this task into a db for example\n\n def queues(self):\n \"\"\"List of queue names for Task consumers\n By default it returns the node name and the task_queues\n in the config dictionary.\n \"\"\"\n queues = [self.backend.node_name]\n queues.extend(self.cfg.task_queues)\n return queues\n\n\n tq = PulsarQueue(Manager, ...)\n\n\nThe ``Manager`` class is initialised when the backend handler is initialised\n(on each consumer and in the scheduler).\n\nChangelog\n==============\n\n* `Versions 0.5 `_\n* `Versions 0.4 `_\n* `Versions 0.3 `_\n* `Versions 0.2 `_\n* `Versions 0.1 `_\n\nLicense\n=============\nThis software is licensed under the BSD 3-clause License. See the LICENSE\nfile in the top distribution directory for the full license text. Logo designed by Ralf Holzemer,\n`creative common license`_.\n\n\n.. _`google user group`: https://groups.google.com/forum/?fromgroups#!forum/python-pulsar\n.. _`Luca Sbardella`: http://lucasbardella.com\n.. _`Quantmind`: http://quantmind.com\n.. _`creative common license`: http://creativecommons.org/licenses/by-nc/3.0/\n.. _pulsar: https://github.com/quantmind/pulsar\n.. _asyncio: https://docs.python.org/3/library/asyncio.html\n.. _greenlet: https://greenlet.readthedocs.io/en/latest/\n.. _msgpack: https://pypi.python.org/pypi/msgpack-python\n.. _`asyncio subprocess`: https://docs.python.org/3/library/asyncio-subprocess.html\n.. _Future: https://docs.python.org/3/library/asyncio-task.html#future\n.. _Job: #the-job-class\n.. _Jobs: #the-job-class\n.. _Task: #the-task\n.. _Events: #events\n.. _events: #events\n.. |pulsar-queue| image:: https://pulsar.fluidily.com/assets/queue/pulsar-queue-banner-400-width.png\n :target: https://github.com/quantmind/pulsar-queue\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/quantmind/pulsar-queue", "keywords": "", "license": "BSD", "maintainer": "", "maintainer_email": "", "name": "pulsar-queue", "package_url": "https://pypi.org/project/pulsar-queue/", "platform": "", "project_url": "https://pypi.org/project/pulsar-queue/", "project_urls": { "Homepage": "https://github.com/quantmind/pulsar-queue" }, "release_url": "https://pypi.org/project/pulsar-queue/0.5.2/", "requires_dist": null, "requires_python": "", "summary": "Asynchronous task queue", "version": "0.5.2" }, "last_serial": 2488350, "releases": { "0.1.0.dev20150925153232": [], "0.1.1": [ { "comment_text": "", "digests": { "md5": "5656069f57526d2d1a65cda5ecdca940", "sha256": "32e506e450b67553ccd3e60c9cebe55b2868410e40ab3c6180085ee41b3f5aaf" }, "downloads": -1, "filename": "pulsar-queue-0.1.1.tar.gz", "has_sig": false, "md5_digest": "5656069f57526d2d1a65cda5ecdca940", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20084, "upload_time": "2015-11-26T16:46:32", "url": "https://files.pythonhosted.org/packages/85/8f/a2b95c0513f6d753a35f4f1b5de14b023c701c1b223a849d62e6866b5b69/pulsar-queue-0.1.1.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "38c52a79950bce71e85ff3c9178ce9f8", "sha256": "94b438aa013ae7bfcba50f900db5c8c1593803a65cb707821d3e66a843d33817" }, "downloads": -1, "filename": "pulsar_queue-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "38c52a79950bce71e85ff3c9178ce9f8", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 26500, "upload_time": "2016-05-12T08:42:01", "url": "https://files.pythonhosted.org/packages/1d/95/ac9782fabf2a3ab67d992c643771a689e539deec0603f756743fd2d15e5c/pulsar_queue-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "74c455b1c39615cb370ca19143379eb0", "sha256": "10fbd83b85e2209624f182b51d6d0b837e0eb4606765162fdf000eb51d7bf042" }, "downloads": -1, "filename": "pulsar-queue-0.2.0.tar.gz", "has_sig": false, "md5_digest": "74c455b1c39615cb370ca19143379eb0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21371, "upload_time": "2016-05-12T08:41:53", "url": "https://files.pythonhosted.org/packages/5f/3a/8bb979ee944d6339ffa93599aa1c9524722e578d894056bfaeb92360dbfc/pulsar-queue-0.2.0.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "3701b7b859062691f646b24167bc4d9b", "sha256": "318a52b0fad2752b0d50522f9c2bf62c4bdd89b31caf637e38f886e53cd26cbd" }, "downloads": -1, "filename": "pulsar_queue-0.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "3701b7b859062691f646b24167bc4d9b", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 48149, "upload_time": "2016-07-01T15:41:21", "url": "https://files.pythonhosted.org/packages/3c/fa/dda4dd599ecf0dc06c1ed5427ea22a2a6fdf668421ec52cf62f1a28899a8/pulsar_queue-0.3.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "338ea2a891d2b661c4d584ffe5ea4d53", "sha256": "3220a4299f047e6d5ceac21fac132b17f1c61df97da6f168ed825c9be25279d3" }, "downloads": -1, "filename": "pulsar-queue-0.3.0.tar.gz", "has_sig": false, "md5_digest": "338ea2a891d2b661c4d584ffe5ea4d53", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23402, "upload_time": "2016-07-01T15:41:16", "url": "https://files.pythonhosted.org/packages/d6/2b/b81a0fff539f558e985d2e9a483c81f65e00f63fed14e52ee1453508c842/pulsar-queue-0.3.0.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "cd2d698fc2d8f3af53b451d4a6d4c7d8", "sha256": "448ab1cf05016c3f29ac5b4927c962d35f732b33f543502be397f7b445f3c72b" }, "downloads": -1, "filename": "pulsar_queue-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "cd2d698fc2d8f3af53b451d4a6d4c7d8", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 52497, "upload_time": "2016-07-09T16:42:00", "url": "https://files.pythonhosted.org/packages/f7/7d/35e421633ffc5e89ec6043084bc19f838ee1f744008d6f9d54f556894105/pulsar_queue-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2758e6502af877e56d8dfec0798f8ccb", "sha256": "d1096ea7b88b1938abf8ad0d3eb879a1d78eeb46d1a93b4e640546046546dc40" }, "downloads": -1, "filename": "pulsar-queue-0.3.1.tar.gz", "has_sig": false, "md5_digest": "2758e6502af877e56d8dfec0798f8ccb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28078, "upload_time": "2016-07-09T16:41:55", "url": "https://files.pythonhosted.org/packages/c0/10/64f52518be6f5e361a3681041c3d0c633e7cf4cde0f57c9f81ba1066ca95/pulsar-queue-0.3.1.tar.gz" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "f524a189487051fd625eab3149b0a7f2", "sha256": "ddbdef1f744a3f93d7f47ae7db9a4ed6e3a72bdb3a7b83ac5fc1bce52f23e5b6" }, "downloads": -1, "filename": "pulsar_queue-0.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "f524a189487051fd625eab3149b0a7f2", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 37650, "upload_time": "2016-08-04T21:58:10", "url": "https://files.pythonhosted.org/packages/41/68/ae6810e3f04cdd518ef6be7a703ce1cc4d7915e016968dc1a7a00ce6fd2c/pulsar_queue-0.4.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ce67622fe27f854fd0085aa96f0b15a6", "sha256": "7b83f66829a78c69f6da42309ca0b87891fc60940bf3b573e0389f19ce8d1dbe" }, "downloads": -1, "filename": "pulsar-queue-0.4.0.tar.gz", "has_sig": false, "md5_digest": "ce67622fe27f854fd0085aa96f0b15a6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31214, "upload_time": "2016-08-04T21:58:07", "url": "https://files.pythonhosted.org/packages/fc/15/d02b33e0311d24f6a6492b57efb5991445d4111d6319ef99cf3cdb8eeffd/pulsar-queue-0.4.0.tar.gz" } ], "0.5.0": [ { "comment_text": "", "digests": { "md5": "b4b94194118b9430dc059b395a798a69", "sha256": "a96d8acb695e2f02926b81eaed5cba8011877423959a673168b7386483900543" }, "downloads": -1, "filename": "pulsar_queue-0.5.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b4b94194118b9430dc059b395a798a69", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 43179, "upload_time": "2016-10-11T15:40:38", "url": "https://files.pythonhosted.org/packages/2f/db/7e56e71ac28c159848531dcf9fc5fd630e9d314c0a8f67b50361a5c12834/pulsar_queue-0.5.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "854c11fcf771eb17b5da77d83fd5d0da", "sha256": "db262eb982f3511aacf4a23e1f6cc4d72d74432e1dc3064677db7ea5a7a4faa8" }, "downloads": -1, "filename": "pulsar-queue-0.5.0.tar.gz", "has_sig": false, "md5_digest": "854c11fcf771eb17b5da77d83fd5d0da", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35492, "upload_time": "2016-10-11T15:40:35", "url": "https://files.pythonhosted.org/packages/7b/ee/70cbce15c92355dc31ce8a20c30b984d1fbac3a24a2b7082ca3687514c43/pulsar-queue-0.5.0.tar.gz" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "2d3734c6c4220332d2a7244cab8a89f7", "sha256": "0c509294acebb92115f097e31ac6a22223df9d6d7783c390812781d961ae92bc" }, "downloads": -1, "filename": "pulsar_queue-0.5.1-py3-none-any.whl", "has_sig": false, "md5_digest": "2d3734c6c4220332d2a7244cab8a89f7", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 43247, "upload_time": "2016-11-25T08:10:27", "url": "https://files.pythonhosted.org/packages/e1/03/0c3b510cc9bc63484665a9edfe5a5cf1b5b582aedf90b0dfdaffdfcd11b1/pulsar_queue-0.5.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d9e8f9016a021a436cfb1f307a5d9554", "sha256": "92ecc6b356915f11bc5121362ba6b176f85f58a8e34a1a6fdc65756b09483ac0" }, "downloads": -1, "filename": "pulsar-queue-0.5.1.tar.gz", "has_sig": false, "md5_digest": "d9e8f9016a021a436cfb1f307a5d9554", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 36016, "upload_time": "2016-11-25T08:10:25", "url": "https://files.pythonhosted.org/packages/55/f4/42cf8ea8479d810492d455457fc28bf33713caf0a580864a085e696f9d20/pulsar-queue-0.5.1.tar.gz" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "55cf9dc7084a61970bb698cbce0e3a83", "sha256": "7273e6e781f7d8dc5c77a7d0c3df82c6697506db3f9d6cb432e32e56e04baedc" }, "downloads": -1, "filename": "pulsar_queue-0.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "55cf9dc7084a61970bb698cbce0e3a83", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 43250, "upload_time": "2016-11-28T20:25:12", "url": "https://files.pythonhosted.org/packages/c4/d0/1bfe7798d2a799e4a519f104bf14ad211b688c4349ccc6a70509f4a7b7e2/pulsar_queue-0.5.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "90b91d4a695b8f823892428a5aa8cd3d", "sha256": "d151138f39dd0ada0c9b32d5fdec69c5a5a5d634e5b1087185fc7d633526ad5b" }, "downloads": -1, "filename": "pulsar-queue-0.5.2.tar.gz", "has_sig": false, "md5_digest": "90b91d4a695b8f823892428a5aa8cd3d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35955, "upload_time": "2016-11-28T20:25:10", "url": "https://files.pythonhosted.org/packages/16/f3/4b22a689b35926ef774e1b395f456c72782c757ddce23112bce437efdde2/pulsar-queue-0.5.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "55cf9dc7084a61970bb698cbce0e3a83", "sha256": "7273e6e781f7d8dc5c77a7d0c3df82c6697506db3f9d6cb432e32e56e04baedc" }, "downloads": -1, "filename": "pulsar_queue-0.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "55cf9dc7084a61970bb698cbce0e3a83", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 43250, "upload_time": "2016-11-28T20:25:12", "url": "https://files.pythonhosted.org/packages/c4/d0/1bfe7798d2a799e4a519f104bf14ad211b688c4349ccc6a70509f4a7b7e2/pulsar_queue-0.5.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "90b91d4a695b8f823892428a5aa8cd3d", "sha256": "d151138f39dd0ada0c9b32d5fdec69c5a5a5d634e5b1087185fc7d633526ad5b" }, "downloads": -1, "filename": "pulsar-queue-0.5.2.tar.gz", "has_sig": false, "md5_digest": "90b91d4a695b8f823892428a5aa8cd3d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35955, "upload_time": "2016-11-28T20:25:10", "url": "https://files.pythonhosted.org/packages/16/f3/4b22a689b35926ef774e1b395f456c72782c757ddce23112bce437efdde2/pulsar-queue-0.5.2.tar.gz" } ] }