{ "info": { "author": "Wes Winham", "author_email": "winhamwr@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing" ], "description": "# jobtastic- Celery tasks plus more awesome\n\n[![Build Status](https://travis-ci.org/PolicyStat/jobtastic.png?branch=master)](https://travis-ci.org/PolicyStat/jobtastic)\n\nJobtastic makes your user-responsive long-running\n[Celery](http://celeryproject.org) jobs totally awesomer.\nCelery is the ubiquitous python job queueing tool\nand jobtastic is a python library\nthat adds useful features to your Celery tasks.\nSpecifically, these are features you probably want\nif the results of your jobs are expensive\nor if your users need to wait while they compute their results.\n\nJobtastic gives you goodies like:\n* Easy progress estimation/reporting\n* Job status feedback\n* Helper methods for gracefully handling a dead task broker\n (`delay_or_eager` and `delay_or_fail`)\n* Super-easy result caching\n* [Thundering herd](http://en.wikipedia.org/wiki/Thundering_herd_problem) avoidance\n* Integration with a\n [celery jQuery plugin](https://github.com/PolicyStat/jquery-celery)\n for easy client-side progress display\n* Memory leak detection in a task run\n\nMake your Celery jobs more awesome with Jobtastic.\n\n## Why Jobtastic?\n\nIf you have user-facing tasks for which a user must wait,\nyou should try Jobtastic.\nIt's great for:\n* Complex reports\n* Graph generation\n* CSV exports\n* Any long-running, user-facing job\n\nYou could write all of the stuff yourself, but why?\n\n## Installation\n\n1. Install gcc and the python C headers\n so that you can build [psutil](https://github.com/giampaolo/psutil/blob/master/INSTALL.rst).\n\n On Ubuntu, that means running:\n\n `$ sudo apt-get install build-essential python-dev python2.7-dev python3.5-dev rabbitmq-server`\n\n On OS X, you'll need to run the \"XcodeTools\" installer.\n\n2. Get the project source and install it\n\n `$ pip install jobtastic`\n\n## Creating Your First Task\n\nLet's take a look at an example task using Jobtastic:\n\n``` python\nfrom time import sleep\n\nfrom jobtastic import JobtasticTask\n\nclass LotsOfDivisionTask(JobtasticTask):\n\t\"\"\"\n\tDivision is hard. Make Celery do it a bunch.\n\t\"\"\"\n\t# These are the Task kwargs that matter for caching purposes\n\tsignificant_kwargs = [\n\t\t('numerators', str),\n\t\t('denominators', str),\n\t]\n\t# How long should we give a task before assuming it has failed?\n\therd_avoidance_timeout = 60 # Shouldn't take more than 60 seconds\n\t# How long we want to cache results with identical ``significant_kwargs``\n\tcache_duration = 0 # Cache these results forever. Math is pretty stable.\n\t# Note: 0 means different things in different cache backends. RTFM for yours.\n\n\tdef calculate_result(self, numerators, denominators, **kwargs):\n\t\t\"\"\"\n\t\tMATH!!!\n\t\t\"\"\"\n\t\tresults = []\n\t\tdivisions_to_do = len(numerators)\n\t\t# Only actually update the progress in the backend every 10 operations\n\t\tupdate_frequency = 10\n\t\tfor count, divisors in enumerate(zip(numerators, denominators)):\n\t\t\tnumerator, denominator = divisors\n\t\t\tresults.append(numerator / denominator)\n\t\t\t# Let's let everyone know how we're doing\n\t\t\tself.update_progress(\n count,\n divisions_to_do,\n update_frequency=update_frequency,\n )\n\t\t\t# Let's pretend that we're using the computers that landed us on the moon\n\t\t\tsleep(0.1)\n\n\t\treturn results\n```\n\nThis task is very trivial,\nbut imagine doing something time-consuming instead of division\n(or just a ton of division)\nwhile a user waited.\nWe wouldn't want a double-clicker to cause this to happen twice concurrently,\nwe wouldn't want to ever redo this work on the same numbers\nand we would want the user to have at least some idea\nof how long they'll need to wait.\nJust by setting those 3 member variables,\nwe've done all of these things.\n\nBasically, creating a Celery task using Jobtastic is a matter of:\n\n1. Subclassing `jobtastic.JobtasticTask`\n2. Defining some required member variables\n3. Writing your `calculate_result` method\n (instead of the normal Celery `run()` method)\n4. Sprinkling `update_progress()` calls in your `calculate_result()` method\n to communicate progress\n\nNow, to use this task in your Django view, you'll do something like:\n\n``` python\nfrom django.shortcuts import render_to_response\n\nfrom my_app.tasks import LotsOfDivisionTask\n\ndef lets_divide(request):\n\t\"\"\"\n\tDo a set number of divisions and keep the user up to date on progress.\n\t\"\"\"\n\titerations = request.GET.get('iterations', 1000) # That's a lot. Right?\n\tstep = 10\n\n\t# If we can't connect to the backend, let's not just 500. k?\n\tresult = LotsOfDivisionTask.delay_or_fail(\n\t\tnumerators=range(0, step * iterations * 2, step * 2),\n\t\tdenominators=range(1, step * iterations, step),\n\t)\n\n\treturn render_to_response(\n\t\t'my_app/lets_divide.html',\n\t\t{'task_id': result.task_id},\n\t)\n```\n\nThe `my_app/lets_divide.html` template will then use the `task_id`\nto query the task result all asynchronous-like\nand keep the user up to date with what is happening.\n\nFor [Flask](http://flask.pocoo.org/), you might do something like:\n\n``` python\nfrom flask import Flask, render_template\n\nfrom my_app.tasks import LotsOfDivisionTask\n\napp = Flask(__name__)\n\n@app.route(\"/\", methods=['GET'])\ndef lets_divide():\n\titerations = request.args.get('iterations', 1000)\n\tstep = 10\n\n\tresult = LotsOfDivisionTask.delay_or_fail(\n\t\tnumerators=range(0, step * iterations * 2, step * 2),\n\t\tdenominators=range(1, step * iterations, step),\n\t)\n\n\treturn render_template('my_app/lets_divide.html', task_id=result.task_id)\n```\n\n### Required Member Variables\n\n\"But wait, Wes. What the heck do those member variables actually do?\" You ask.\n\nFirstly. How the heck did you know my name?\n\nAnd B, why don't I tell you!?\n\n#### significant_kwargs\n\nThis is key to your caching magic.\nIt's a list of 2-tuples containing the name of a kwarg\nplus a function to turn that kwarg in to a string.\nJobtastic uses these to determine if your task\nshould have an identical result to another task run.\nIn our division example,\nany task with the same numerators and denominators can be considered identical,\nso Jobtastic can do smart things.\n\n``` python\nsignificant_kwargs = [\n\t('numerators', str),\n\t('denominators', str),\n]\n```\n\nIf we were living in bizzaro world,\nand only the numerators mattered for division results,\nwe could do something like:\n\n``` python\nsignificant_kwargs = [\n\t('numerators', str),\n]\n```\n\nNow tasks called with an identical list of numerators will share a result.\n\n#### herd_avoidance_timeout\n\nThis is the max number of seconds for which Jobtastic will wait\nfor identical task results to be determined.\nYou want this number to be on the very high end\nof the amount of time you expect to wait\n(after a task starts)\nfor the result.\nIf this number is hit,\nit's assumed that something bad happened to the other task run\n(a worker failed)\nand we'll start calculating from the start.\n\n### Optional Member Variables\n\nThese let you tweak the default behavior.\nMost often, you'll just be setting the `cache_duration`\nto enable result caching.\n\n#### cache_duration\n\nIf you want your results cached,\nset this to a non-negative number of seconds.\nThis is the number of seconds for which identical jobs\nshould try to just re-use the cached result.\nThe default is -1,\nmeaning don't do any caching.\nRemember,\n`JobtasticTask` uses your `significant_kwargs` to determine what is identical.\n\n#### cache_prefix\n\nThis is an optional string used to represent tasks\nthat should share cache results and thundering herd avoidance.\nYou should almost never set this yourself,\nand instead should let Jobtastic use the `module.class` name.\nIf you have two different tasks that should share caching,\nor you have some very-odd cache key conflict,\nthen you can change this yourself.\nYou probably don't need to.\n\n#### memleak_threshold\n\nSet this value to monitor your tasks\nfor any runs that increase the memory usage\nby more than this number of Megabytes\n(the SI definition).\nIndividual task runs that increase resident memory\nby more than this threshold\nget some extra logging\nin order to help you debug the problem.\nBy default, it logs the following via standard Celery logging:\n * The memory increase\n * The memory starting value\n * The memory ending value\n * The task's kwargs\n\nYou then grep for `Jobtastic:memleak memleak_detected` in your logs\nto identify offending tasks.\n\nIf you'd like to customize this behavior,\nyou can override the `warn_of_memory_leak` method in your own `Task`.\n\n### Method to Override\n\nOther than tweaking the member variables,\nyou'll probably want to actually, you know,\n*do something* in your task.\n\n#### calculate_result\n\nThis is where your magic happens.\nDo work here and return the result.\n\nYou'll almost definitely want to\ncall `update_progress` periodically in this method\nso that your users get an idea of for how long they'll be waiting.\n\n### Progress feedback helper\n\nThis is the guy you'll want to call\nto provide nice progress feedback and estimation.\n\n#### update_progress\n\nIn your `calculate_result`,\nyou'll want to periodically make calls like:\n\n``` python\nself.update_progress(work_done, total_work_to_do)\n```\n\nJobtastic takes care of handling timers to give estimates,\nand assumes that progress will be roughly uniform across each work item.\n\nMost of the time,\nyou really don't need ultra-granular progress updates\nand can afford to only give an update every `N` items completed.\nSince every update would potentially hit your\n[CELERY_RESULT_BACKEND](http://celery.github.com/celery/configuration.html#celery-result-backend),\nand that might cause a network trip,\nit's probably a good idea to use the optional `update_frequency` argument\nso that Jobtastic doesn't swamp your backend\nwith updated estimates no user will ever see.\n\nIn our division example,\nwe're only actually updating the progress every 10 division operations:\n\n``` python\n# Only actually update the progress in the backend every 10 operations\nupdate_frequency = 10\nfor count, divisors in enumerate(zip(numerators, denominators)):\n\tnumerator, denominator = divisors\n\tresults.append(numerator / denominator)\n\t# Let's let everyone know how we're doing\n\tself.update_progress(count, divisions_to_do, update_frequency=10)\n```\n\n## Using your JobtasticTask\n\nSometimes,\nyour [Task Broker](http://celery.github.com/celery/configuration.html#broker-url)\njust up and dies\n(I'm looking at you, old versions of RabbitMQ).\nIn production,\ncalling straight up `delay()` with a dead backend\nwill throw an error that varies based on what backend you're actually using.\nYou probably don't want to just give your user a generic 500 page\nif your broker is down,\nand it's not fun to handle that exception every single place\nyou might use Celery.\nJobtastic has your back.\n\nIncluded are `delay_or_eager` and `delay_or_fail` methods\nthat handle a dead backend\nand do something a little more production-friendly.\n\nNote: One very important caveat with `JobtasticTask` is that\nall of your arguments must be keyword arguments.\n\nNote: This is a limitation of the current `significant_kwargs` implementation,\nand totally fixable if someone wants to submit a pull request.\n\n### delay_or_eager\n\nIf your broker is behaving itself,\nthis guy acts just like `delay()`.\nIn the case that your broker is down,\nthough,\nit just goes ahead and runs the task in the current process\nand skips sending the task to a worker.\nYou get back a nice shiny `EagerResult` object,\nwhich behaves just like the `AsyncResult` you were expecting.\nIf you have a task that realistically only takes a few seconds to run,\nthis might be better than giving yours users an error message.\n\nThis method uses `async_or_eager()` under the hood.\n\n### delay_or_fail\n\nLike `delay_or_eager`,\nthis helps you handle a dead broker.\nInstead of running your task in the current process,\nthis actually generates a task result representing the failure.\nThis means that your client-side code can handle it\nlike any other failed task\nand do something nice for the user.\nMaybe send them a fruit basket?\n\nFor tasks that might take a while\nor consume a lot of RAM,\nyou're probably better off using this than `delay_or_eager`\nbecause you don't want to make a resource problem worse.\n\nThis method uses `async_or_fail()` under the hood.\n\n### async_or_eager\n\nThis is a version of `delay_or_eager()` that exposes the calling signature\nof `apply_async()`.\n\n### async_or_fail\n\nThis is a version of `delay_or_fail()` that exposes the calling signature\nof `apply_async()`.\n\n## Client Side Handling\n\nThat's all well and good on the server side,\nbut the biggest benefit of Jobtastic is useful user-facing feedback.\nThat means handling status checks using AJAX in the browser.\n\nThe easiest way to get rolling is to use our sister project,\n[jquery-celery](https://github.com/PolicyStat/jquery-celery).\nIt contains jQuery plugins that help you:\n* Poll for task status and handle the result\n* Display a progress bar using the info from the `PROGRESS` state.\n* Display tabular data using [DataTables](http://www.datatables.net/).\n\nIf you want to roll your own,\nthe general pattern is to poll a URL\n(such as the django-celery\n[task_status view](https://github.com/celery/django-celery/blob/master/djcelery/urls.py#L25) )\nwith your taskid to get JSON status information\nand then handle the possible states to keep the user informed.\n\nThe [jquery-celery](https://github.com/PolicyStat/jquery-celery/blob/master/src/celery.js)\njQuery plugin might still be useful as reference,\neven if you're rolling your own.\nIn general, you'll want to handle the following cases:\n\n### PENDING\n\nYour task is still waiting for a worker process.\nIt's generally useful to display something like \"Waiting for your task to begin\".\n\n### PROGRESS\n\nYour task has started and you've got a JSON object like:\n\n``` javascript\n{\n\t\"progress_percent\": 0,\n\t\"time_remaining\": 300\n}\n```\n\n`progress_percent` is a number between 0 and 100.\nIt's a good idea to give a different message if the percent is 0,\nbecause the time remaining estimate might not yet be well-calibrated.\n\n`time_remaining` is the number of seconds estimated to be left.\nIf there's no good estimate available, this value will be `-1`.\n\n### SUCCESS\n\nYou've got your data. It's time to display the result.\n\n### FAILURE\n\nSomething went wrong and the worker reported a failure.\nThis is a good time to either display a useful error message\n(if the user can be expected to correct the problem),\nor to ask the user to retry their task.\n\n### Non-200 Request\n\nThere are occasions where requesting the task status itself might error out.\nThis isn't a reflection on the worker itself,\nas it could be caused by any number of application errors.\nIn general, you probably want to try again if this happens,\nbut if it persists, you'll want to give your user feedback.\n\n## Running The Test Suite\n\nWe use [tox](https://tox.readthedocs.org/en/latest/)\nto run our tests against various combinations\nof python/Django/Celery.\nWe only officially support\nthe combinations listed in our `.travis.yml` file,\nbut we're working on\n([Issue 33](https://github.com/PolicyStat/jobtastic/issues/33))\nsupporting everything defined in `tox.ini`.\nUntil then,\nyou can run tests against supported combos with:\n\n $ pip install tox\n $ tox -e py27-django1.8.X-djangocelery3.1.X-celery3.1.X\n\nOur test suite currently only tests usage with Django,\nwhich is definitely a [bug](https://github.com/PolicyStat/jobtastic/issues/15).\nEspecially if you use Jobtastic with Flask,\nwe would love a pull request.\n\n## Dynamic Time Estimates via JobtasticMixins\n\nHave tasks whose duration is difficult to estimate\nor that doesn't have smooth progress?\n[JobtasticMixins](https://github.com/abbasovalex/JobtasticMixins)\nto the rescue!\n\nJobtasticMixins provides an `AVGTimeRedis` mixin\nthat stores duration date in a Redis backend.\nIt then automatically uses this stored historical data\nto calculate an estimate.\nFor more details,\ncheck out [JobtasticMixins](https://github.com/abbasovalex/JobtasticMixins)\non github.\n\n## Is it Awesome?\n\nYes. Increasingly so.\n\n## Project Status\n\nJobtastic is currently known to work\nwith Django 1.6+ and Celery 3.1.X\nThe goal is to support those versions and newer.\nPlease file issues if there are problems\nwith newer versions of Django/Celery.\n\n### Gotchas\n\nAt this time of this writing,\nthe latest supported version of kombu\nwith celery 4.x is\n4.0.2.\nThis is due to an issue with invalid\nor temporarily broken\nbrokers with the newer versions of kombu.\n\nAlso, `RabbitMQ` should be running in the background while running tests.\n\n### A note on usage with Flask\n\nPreviously,\nif you were using Flask instead of Django,\nthen the only currently-supported way to work with Jobtastic\nwas with Memcached as your `CELERY_RESULT_BACKEND`.\n\nThanks to @rhunwicks this is no longer the case!\n\nA cache is now selected with the following priority:\n\n* If the Celery appconfig has a `JOBTASTIC_CACHE` setting and it is a valid cache, use it\n* If Django is installed, then:\n - If the setting is a valid Django cache entry, then use that.\n - If the setting is empty use the default cache\n* If Werkzeug is installed, then:\n - If the setting is a valid Celery Memcache or Redis Backend, then use that.\n - If the setting is empty and the default Celery Result Backend is Memcache or Redis, then use that\n\n## Non-affiliation\n\nThis project isn't affiliated with the awesome folks at the\n[Celery Project](http://www.celeryproject.org)\n(unless having a huge crush counts as affiliation).\nIt's a library that the folks at [PolicyStat](http://www.policystat.com)\nhave been using internally\nand decided to open source in the hopes it is useful to others.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://policystat.github.com/jobtastic", "keywords": "", "license": "BSD", "maintainer": "", "maintainer_email": "", "name": "jobtastic", "package_url": "https://pypi.org/project/jobtastic/", "platform": "any", "project_url": "https://pypi.org/project/jobtastic/", "project_urls": { "Homepage": "http://policystat.github.com/jobtastic" }, "release_url": "https://pypi.org/project/jobtastic/2.1.1/", "requires_dist": [ "psutil", "celery (<5,>=3.1)" ], "requires_python": "", "summary": "Make your user-facing Celery jobs totally awesomer", "version": "2.1.1" }, "last_serial": 5242925, "releases": { "0.1.0b1": [ { "comment_text": "", "digests": { "md5": "cd1abb984cc79674c80807a3d7eee1a8", "sha256": "186a9d40c7525ec019138064db0cde56952138547c4affd19e18948f627d2c2f" }, "downloads": -1, "filename": "jobtastic-0.1.0b1.tar.gz", "has_sig": false, "md5_digest": "cd1abb984cc79674c80807a3d7eee1a8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14202, "upload_time": "2012-08-31T16:04:40", "url": "https://files.pythonhosted.org/packages/88/79/6bd0120306efa0137f6005d8adaed517cf1f7aec345f0e0cf286916abc60/jobtastic-0.1.0b1.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "f222fec7b7dc257641c6dec5425d8336", "sha256": "af3f81a25323ac904365f6d3c4f34f91e5947e0254dd2501f3ba612f5cf02d7f" }, "downloads": -1, "filename": "jobtastic-0.1.1.tar.gz", "has_sig": false, "md5_digest": "f222fec7b7dc257641c6dec5425d8336", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25273, "upload_time": "2013-04-05T20:41:40", "url": "https://files.pythonhosted.org/packages/2d/41/48540eafab9d14b12e0686284942e2b9611bc61a32c4ee4ed89e39831367/jobtastic-0.1.1.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "38bb9dde579122919433271e2f61bf5e", "sha256": "f2a2b5e63e1d0143a891e61db224b5006bb31242e56667fcb1769f32148aa288" }, "downloads": -1, "filename": "jobtastic-0.2.0.tar.gz", "has_sig": false, "md5_digest": "38bb9dde579122919433271e2f61bf5e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27632, "upload_time": "2013-04-13T00:59:28", "url": "https://files.pythonhosted.org/packages/03/6e/5754a0c483382b792b815d371312767494f53fb0027972096e6135a74546/jobtastic-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "1513324522dc6fcf61bae5efa5f5eab0", "sha256": "75747a14ab56958f30723fe56d977abe2bc68934765713f164a213b5b67b74e2" }, "downloads": -1, "filename": "jobtastic-0.2.1.tar.gz", "has_sig": false, "md5_digest": "1513324522dc6fcf61bae5efa5f5eab0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28101, "upload_time": "2013-04-25T22:29:48", "url": "https://files.pythonhosted.org/packages/0f/50/37136ab1372be5372e74bd285fc1168aec6bedb6d78bccbd1aa31c9a325f/jobtastic-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "05ae6a7a1c70a3b4940b9f438fcce4da", "sha256": "6cc4a38604439533487b51fb03d3062f545355631d37554d6c7a093bb102c57e" }, "downloads": -1, "filename": "jobtastic-0.2.2.tar.gz", "has_sig": false, "md5_digest": "05ae6a7a1c70a3b4940b9f438fcce4da", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24738, "upload_time": "2013-08-29T21:25:05", "url": "https://files.pythonhosted.org/packages/3c/57/90aa91c437ec7526569a725f04cc4d5d832c872632a31773e01b22087886/jobtastic-0.2.2.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "e43f1017fd76726955dd8afdf78a1a04", "sha256": "311e83bce5ebe960e52687b6d7bc53d734022227a4c95347736e626c387a8c57" }, "downloads": -1, "filename": "jobtastic-0.3.1.tar.gz", "has_sig": false, "md5_digest": "e43f1017fd76726955dd8afdf78a1a04", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25800, "upload_time": "2015-06-30T19:05:51", "url": "https://files.pythonhosted.org/packages/e4/e8/4f3ff5f63aedfb7d03c662a89c7e8fc4bd8fd07d4ecc5bb871462159bacf/jobtastic-0.3.1.tar.gz" } ], "1.0.0a1": [ { "comment_text": "", "digests": { "md5": "1f1b9d58f558c1ca327ee0e93bd65c7c", "sha256": "2a157ac4e5c0a419f8dd8686f47bc45f2ba5c9035c97574494582c9e795befa8" }, "downloads": -1, "filename": "jobtastic-1.0.0a1.tar.gz", "has_sig": false, "md5_digest": "1f1b9d58f558c1ca327ee0e93bd65c7c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25892, "upload_time": "2016-09-01T17:34:19", "url": "https://files.pythonhosted.org/packages/80/5a/a49c36bb0ab1e13fc8b5e9409fc3850c68c80096506e78a8b82190969563/jobtastic-1.0.0a1.tar.gz" } ], "1.0.0a2": [ { "comment_text": "", "digests": { "md5": "a44d05fe4134169cebe8d54b4f320aac", "sha256": "6ce2e0005f18ccb2bcb0f1c8fdb9bf2e552c852cbb9e25ba5bccd50f32dd668e" }, "downloads": -1, "filename": "jobtastic-1.0.0a2.tar.gz", "has_sig": false, "md5_digest": "a44d05fe4134169cebe8d54b4f320aac", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37466, "upload_time": "2017-03-16T16:07:41", "url": "https://files.pythonhosted.org/packages/b6/cc/06c04404e33163f7e6c3e9a1983399301c84afde0364d18dee62f5c32351/jobtastic-1.0.0a2.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "0314659d9120639adf6e86f4ce05c79a", "sha256": "db02df8e7ed3fe7b8d9bb295d51d53c97f84a52152d9734ef060d212c413ee8a" }, "downloads": -1, "filename": "jobtastic-2.0.0.tar.gz", "has_sig": false, "md5_digest": "0314659d9120639adf6e86f4ce05c79a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38094, "upload_time": "2017-12-11T19:31:41", "url": "https://files.pythonhosted.org/packages/f8/de/5d34a9f359bd46215355afcdfde6d048521f892b6ff9300894e3efff5bd4/jobtastic-2.0.0.tar.gz" } ], "2.1.0": [ { "comment_text": "", "digests": { "md5": "a03d2d361bcd56508768492e6fdad3c6", "sha256": "94224d8420f4eaf8cb4433d94813d6a2adb5c4c281d73166156b176dcf790102" }, "downloads": -1, "filename": "jobtastic-2.1.0.tar.gz", "has_sig": false, "md5_digest": "a03d2d361bcd56508768492e6fdad3c6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31834, "upload_time": "2019-04-23T19:30:14", "url": "https://files.pythonhosted.org/packages/ba/18/60a1bd8120d34032d3ba60fd41fd3b5f3e862b777554c6b7a8e666021999/jobtastic-2.1.0.tar.gz" } ], "2.1.1": [ { "comment_text": "", "digests": { "md5": "532845f58cbf22317b537a052da37157", "sha256": "9c7ff18464a8db0a9f9bc4afaeb97af9d1b6d1896f2f2e98740ad1e66c306f9e" }, "downloads": -1, "filename": "jobtastic-2.1.1-py2-none-any.whl", "has_sig": false, "md5_digest": "532845f58cbf22317b537a052da37157", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 23006, "upload_time": "2019-05-08T13:49:13", "url": "https://files.pythonhosted.org/packages/bb/ba/3abd39899df0232eaa5d4ae715ae1e20b7999d49d8ae2020bd4573cf1662/jobtastic-2.1.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c84f37a03e925df644fc757c372f706f", "sha256": "25b98692183a9254ede363129bbe89d505b7396badd87a5c0a5ba338512aade1" }, "downloads": -1, "filename": "jobtastic-2.1.1.tar.gz", "has_sig": false, "md5_digest": "c84f37a03e925df644fc757c372f706f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33663, "upload_time": "2019-05-08T13:49:15", "url": "https://files.pythonhosted.org/packages/2b/98/02b10f6b00dd62accd4948be44d2266a6b2f84b5d4f0663d4d5e60cbfc5b/jobtastic-2.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "532845f58cbf22317b537a052da37157", "sha256": "9c7ff18464a8db0a9f9bc4afaeb97af9d1b6d1896f2f2e98740ad1e66c306f9e" }, "downloads": -1, "filename": "jobtastic-2.1.1-py2-none-any.whl", "has_sig": false, "md5_digest": "532845f58cbf22317b537a052da37157", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 23006, "upload_time": "2019-05-08T13:49:13", "url": "https://files.pythonhosted.org/packages/bb/ba/3abd39899df0232eaa5d4ae715ae1e20b7999d49d8ae2020bd4573cf1662/jobtastic-2.1.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c84f37a03e925df644fc757c372f706f", "sha256": "25b98692183a9254ede363129bbe89d505b7396badd87a5c0a5ba338512aade1" }, "downloads": -1, "filename": "jobtastic-2.1.1.tar.gz", "has_sig": false, "md5_digest": "c84f37a03e925df644fc757c372f706f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33663, "upload_time": "2019-05-08T13:49:15", "url": "https://files.pythonhosted.org/packages/2b/98/02b10f6b00dd62accd4948be44d2266a6b2f84b5d4f0663d4d5e60cbfc5b/jobtastic-2.1.1.tar.gz" } ] }