├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── TODO ├── docs ├── Makefile ├── _static │ └── retask-sidebar.png ├── _templates │ ├── sidebarintro.html │ └── sidebarlogo.html ├── _themes │ ├── LICENSE │ ├── README.rst │ ├── flask_theme_support.py │ ├── kr │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ │ ├── flasky.css_t │ │ │ └── small_flask.css │ │ └── theme.conf │ └── kr_small │ │ ├── layout.html │ │ ├── static │ │ └── flasky.css_t │ │ └── theme.conf ├── api.rst ├── conf.py ├── index.rst ├── retask.queue.rst ├── retask.task.rst └── user │ ├── install.rst │ ├── intro.rst │ ├── quickstart.rst │ ├── redis.rst │ └── tutorials.rst ├── examples ├── async_producer.py ├── async_worker.py ├── consumer.py ├── producer.py └── sync_producer.py ├── requirements.txt ├── retask ├── __init__.py ├── exceptions.py ├── queue.py └── task.py ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.egg 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012, Kushal Das 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include TODO 3 | recursive-include docs * 4 | include examples/*.py 5 | include tests.py 6 | include LICENSE 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Welcome to retask 2 | ================= 3 | retask is a python module to create distributed task 4 | queues using Redis. 5 | 6 | You can read the latest documentation `here `_. 7 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Manager Class: 2 | * To view all tasks in a given queue 3 | * To delete a queue 4 | 5 | retask-monitor 6 | * command line tool to monitor different queues in the system 7 | * Print all queue names 8 | * Show details of a queue (number of tasks) 9 | 10 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/retask.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/retask.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/retask" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/retask" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_static/retask-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/retask/9ae604c51e38e75cc1075e2d206c481df40b887c/docs/_static/retask-sidebar.png -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

8 | 10 |

11 | 12 |

13 | Retask is a simple task queue implementation 14 | written for human beings. It provides generic 15 | solution to create and manage task queues. 16 |

17 | 18 |

19 | Feedback is greatly appreciated. If you have any questions, comments, 20 | random praise, or anonymous threats, 21 | shoot me an email. 22 |

23 | 24 | 25 |

Useful Links

26 | 30 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 6 |

7 | 9 |

10 | 11 |

12 | Retask is a simple task queue implementation 13 | written for human beings. It provides generic 14 | solution to create and manage task queues. 15 |

16 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2010 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /docs/_themes/README.rst: -------------------------------------------------------------------------------- 1 | krTheme Sphinx Style 2 | ==================== 3 | 4 | This repository contains sphinx styles Kenneth Reitz uses in most of 5 | his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related 6 | projects. To use this style in your Sphinx documentation, follow 7 | this guide: 8 | 9 | 1. put this folder as _themes into your docs folder. Alternatively 10 | you can also use git submodules to check out the contents there. 11 | 12 | 2. add this to your conf.py: :: 13 | 14 | sys.path.append(os.path.abspath('_themes')) 15 | html_theme_path = ['_themes'] 16 | html_theme = 'kr' 17 | 18 | The following themes exist: 19 | 20 | **kr** 21 | the standard flask documentation theme for large projects 22 | 23 | **kr_small** 24 | small one-page theme. Intended to be used by very small addon libraries. 25 | 26 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/_themes/kr/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | 10 | {% endblock %} 11 | {%- block relbar2 %}{% endblock %} 12 | {%- block footer %} 13 | 16 | 17 | Fork me on GitHub 18 | 19 | 32 | 33 | {%- endblock %} 34 | -------------------------------------------------------------------------------- /docs/_themes/kr/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_themes/kr/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 18px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0; 95 | margin: -10px 0 0 -20px; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #444; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body h1, 154 | div.body h2, 155 | div.body h3, 156 | div.body h4, 157 | div.body h5, 158 | div.body h6 { 159 | font-family: 'Garamond', 'Georgia', serif; 160 | font-weight: normal; 161 | margin: 30px 0px 10px 0px; 162 | padding: 0; 163 | } 164 | 165 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 166 | div.body h2 { font-size: 180%; } 167 | div.body h3 { font-size: 150%; } 168 | div.body h4 { font-size: 130%; } 169 | div.body h5 { font-size: 100%; } 170 | div.body h6 { font-size: 100%; } 171 | 172 | a.headerlink { 173 | color: #ddd; 174 | padding: 0 4px; 175 | text-decoration: none; 176 | } 177 | 178 | a.headerlink:hover { 179 | color: #444; 180 | background: #eaeaea; 181 | } 182 | 183 | div.body p, div.body dd, div.body li { 184 | line-height: 1.4em; 185 | } 186 | 187 | div.admonition { 188 | background: #fafafa; 189 | margin: 20px -30px; 190 | padding: 10px 30px; 191 | border-top: 1px solid #ccc; 192 | border-bottom: 1px solid #ccc; 193 | } 194 | 195 | div.admonition tt.xref, div.admonition a tt { 196 | border-bottom: 1px solid #fafafa; 197 | } 198 | 199 | dd div.admonition { 200 | margin-left: -60px; 201 | padding-left: 60px; 202 | } 203 | 204 | div.admonition p.admonition-title { 205 | font-family: 'Garamond', 'Georgia', serif; 206 | font-weight: normal; 207 | font-size: 24px; 208 | margin: 0 0 10px 0; 209 | padding: 0; 210 | line-height: 1; 211 | } 212 | 213 | div.admonition p.last { 214 | margin-bottom: 0; 215 | } 216 | 217 | div.highlight { 218 | background-color: white; 219 | } 220 | 221 | dt:target, .highlight { 222 | background: #FAF3E8; 223 | } 224 | 225 | div.note { 226 | background-color: #eee; 227 | border: 1px solid #ccc; 228 | } 229 | 230 | div.seealso { 231 | background-color: #ffc; 232 | border: 1px solid #ff6; 233 | } 234 | 235 | div.topic { 236 | background-color: #eee; 237 | } 238 | 239 | p.admonition-title { 240 | display: inline; 241 | } 242 | 243 | p.admonition-title:after { 244 | content: ":"; 245 | } 246 | 247 | pre, tt { 248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 249 | font-size: 0.9em; 250 | } 251 | 252 | img.screenshot { 253 | } 254 | 255 | tt.descname, tt.descclassname { 256 | font-size: 0.95em; 257 | } 258 | 259 | tt.descname { 260 | padding-right: 0.08em; 261 | } 262 | 263 | img.screenshot { 264 | -moz-box-shadow: 2px 2px 4px #eee; 265 | -webkit-box-shadow: 2px 2px 4px #eee; 266 | box-shadow: 2px 2px 4px #eee; 267 | } 268 | 269 | table.docutils { 270 | border: 1px solid #888; 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils td, table.docutils th { 277 | border: 1px solid #888; 278 | padding: 0.25em 0.7em; 279 | } 280 | 281 | table.field-list, table.footnote { 282 | border: none; 283 | -moz-box-shadow: none; 284 | -webkit-box-shadow: none; 285 | box-shadow: none; 286 | } 287 | 288 | table.footnote { 289 | margin: 15px 0; 290 | width: 100%; 291 | border: 1px solid #eee; 292 | background: #fdfdfd; 293 | font-size: 0.9em; 294 | } 295 | 296 | table.footnote + table.footnote { 297 | margin-top: -15px; 298 | border-top: none; 299 | } 300 | 301 | table.field-list th { 302 | padding: 0 0.8em 0 0; 303 | } 304 | 305 | table.field-list td { 306 | padding: 0; 307 | } 308 | 309 | table.footnote td.label { 310 | width: 0px; 311 | padding: 0.3em 0 0.3em 0.5em; 312 | } 313 | 314 | table.footnote td { 315 | padding: 0.3em 0.5em; 316 | } 317 | 318 | dl { 319 | margin: 0; 320 | padding: 0; 321 | } 322 | 323 | dl dd { 324 | margin-left: 30px; 325 | } 326 | 327 | blockquote { 328 | margin: 0 0 0 30px; 329 | padding: 0; 330 | } 331 | 332 | ul, ol { 333 | margin: 10px 0 10px 30px; 334 | padding: 0; 335 | } 336 | 337 | pre { 338 | background: #eee; 339 | padding: 7px 30px; 340 | margin: 15px -30px; 341 | line-height: 1.3em; 342 | } 343 | 344 | dl pre, blockquote pre, li pre { 345 | margin-left: -60px; 346 | padding-left: 60px; 347 | } 348 | 349 | dl dl pre { 350 | margin-left: -90px; 351 | padding-left: 90px; 352 | } 353 | 354 | tt { 355 | background-color: #ecf0f3; 356 | color: #222; 357 | /* padding: 1px 2px; */ 358 | } 359 | 360 | tt.xref, a tt { 361 | background-color: #FBFBFB; 362 | border-bottom: 1px solid white; 363 | } 364 | 365 | a.reference { 366 | text-decoration: none; 367 | border-bottom: 1px dotted #004B6B; 368 | } 369 | 370 | a.reference:hover { 371 | border-bottom: 1px solid #6D4100; 372 | } 373 | 374 | a.footnote-reference { 375 | text-decoration: none; 376 | font-size: 0.7em; 377 | vertical-align: top; 378 | border-bottom: 1px dotted #004B6B; 379 | } 380 | 381 | a.footnote-reference:hover { 382 | border-bottom: 1px solid #6D4100; 383 | } 384 | 385 | a:hover tt { 386 | background: #EEE; 387 | } 388 | 389 | 390 | @media screen and (max-width: 600px) { 391 | 392 | div.sphinxsidebar { 393 | display: none; 394 | } 395 | 396 | div.document { 397 | width: 100%; 398 | 399 | } 400 | 401 | div.documentwrapper { 402 | margin-left: 0; 403 | margin-top: 0; 404 | margin-right: 0; 405 | margin-bottom: 0; 406 | } 407 | 408 | div.bodywrapper { 409 | margin-top: 0; 410 | margin-right: 0; 411 | margin-bottom: 0; 412 | margin-left: 0; 413 | } 414 | 415 | ul { 416 | margin-left: 0; 417 | } 418 | 419 | .document { 420 | width: auto; 421 | } 422 | 423 | .footer { 424 | width: auto; 425 | } 426 | 427 | .bodywrapper { 428 | margin: 0; 429 | } 430 | 431 | .footer { 432 | width: auto; 433 | } 434 | 435 | .github { 436 | display: none; 437 | } 438 | 439 | } 440 | 441 | /* misc. */ 442 | 443 | .revsys-inline { 444 | display: none!important; 445 | } -------------------------------------------------------------------------------- /docs/_themes/kr/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | 72 | .rtd_doc_footer { 73 | display: none; 74 | } 75 | 76 | .document { 77 | width: auto; 78 | } 79 | 80 | .footer { 81 | width: auto; 82 | } 83 | 84 | .footer { 85 | width: auto; 86 | } 87 | 88 | .github { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /docs/_themes/kr/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | .. module:: retask 7 | 8 | This part contains the API documentation of the module. 9 | 10 | Submodules 11 | ----------- 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | retask.queue 16 | retask.task 17 | 18 | Exceptions 19 | ---------- 20 | 21 | .. autoexception:: retask.RetaskException 22 | .. autoexception:: retask.ConnectionError 23 | 24 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # retask documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 3 14:56:38 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 16 | 17 | sys.path.append(os.path.abspath('_themes')) 18 | html_theme_path = ['_themes'] 19 | html_theme = 'kr' 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ----------------------------------------------------- 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be extensions 31 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 32 | extensions = ['sphinx.ext.intersphinx', 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.doctest', 'sphinx.ext.coverage'] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = u'retask' 48 | copyright = u'2012-2016, Kushal Das' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '1.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '1.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | #pygments_style = 'flask_theme_support.FlaskyStyle' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | html_sidebars = { 98 | 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], 99 | '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 100 | 'sourcelink.html', 'searchbox.html'] 101 | } 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | #html_theme = 'default' 106 | 107 | # Theme options are theme-specific and customize the look and feel of a theme 108 | # further. For a list of options available for each theme, see the 109 | # documentation. 110 | #html_theme_options = {} 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | #html_theme_path = [] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 137 | # using the given strftime format. 138 | #html_last_updated_fmt = '%b %d, %Y' 139 | 140 | # If true, SmartyPants will be used to convert quotes and dashes to 141 | # typographically correct entities. 142 | #html_use_smartypants = True 143 | 144 | # Custom sidebar templates, maps document names to template names. 145 | #html_sidebars = {} 146 | 147 | # Additional templates that should be rendered to pages, maps page names to 148 | # template names. 149 | #html_additional_pages = {} 150 | 151 | # If false, no module index is generated. 152 | #html_domain_indices = True 153 | 154 | # If false, no index is generated. 155 | #html_use_index = True 156 | 157 | # If true, the index is split into individual pages for each letter. 158 | #html_split_index = False 159 | 160 | # If true, links to the reST sources are added to the pages. 161 | #html_show_sourcelink = True 162 | 163 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 164 | #html_show_sphinx = True 165 | 166 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 167 | #html_show_copyright = True 168 | 169 | # If true, an OpenSearch description file will be output, and all pages will 170 | # contain a tag referring to it. The value of this option must be the 171 | # base URL from which the finished HTML is served. 172 | #html_use_opensearch = '' 173 | 174 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 175 | #html_file_suffix = None 176 | 177 | # Output file base name for HTML help builder. 178 | htmlhelp_basename = 'retaskdoc' 179 | 180 | 181 | # -- Options for LaTeX output -------------------------------------------------- 182 | 183 | latex_elements = { 184 | # The paper size ('letterpaper' or 'a4paper'). 185 | #'papersize': 'letterpaper', 186 | 187 | # The font size ('10pt', '11pt' or '12pt'). 188 | #'pointsize': '10pt', 189 | 190 | # Additional stuff for the LaTeX preamble. 191 | #'preamble': '', 192 | } 193 | 194 | # Grouping the document tree into LaTeX files. List of tuples 195 | # (source start file, target name, title, author, documentclass [howto/manual]). 196 | latex_documents = [ 197 | ('index', 'retask.tex', u'retask Documentation', 198 | u'Kushal Das', 'manual'), 199 | ] 200 | 201 | # The name of an image file (relative to this directory) to place at the top of 202 | # the title page. 203 | #latex_logo = None 204 | 205 | # For "manual" documents, if this is true, then toplevel headings are parts, 206 | # not chapters. 207 | #latex_use_parts = False 208 | 209 | # If true, show page references after internal links. 210 | #latex_show_pagerefs = False 211 | 212 | # If true, show URL addresses after external links. 213 | #latex_show_urls = False 214 | 215 | # Documents to append as an appendix to all manuals. 216 | #latex_appendices = [] 217 | 218 | # If false, no module index is generated. 219 | #latex_domain_indices = True 220 | 221 | 222 | # -- Options for manual page output -------------------------------------------- 223 | 224 | # One entry per manual page. List of tuples 225 | # (source start file, name, description, authors, manual section). 226 | man_pages = [ 227 | ('index', 'retask', u'retask Documentation', 228 | [u'Kushal Das'], 1) 229 | ] 230 | 231 | # If true, show URL addresses after external links. 232 | #man_show_urls = False 233 | 234 | 235 | # -- Options for Texinfo output ------------------------------------------------ 236 | 237 | # Grouping the document tree into Texinfo files. List of tuples 238 | # (source start file, target name, title, author, 239 | # dir menu entry, description, category) 240 | texinfo_documents = [ 241 | ('index', 'retask', u'retask Documentation', 242 | u'Kushal Das', 'retask', 'One line description of project.', 243 | 'Miscellaneous'), 244 | ] 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #texinfo_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #texinfo_domain_indices = True 251 | 252 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 253 | #texinfo_show_urls = 'footnote' 254 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. retask documentation master file, created by 2 | sphinx-quickstart on Tue Jul 3 14:56:38 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Retask: simple Task Queue 7 | ============================= 8 | retask is a python module to create and manage distributed task queue/job queue. 9 | 10 | It uses `Redis `_ to create task queues. User can enqueue 11 | and dequeue tasks in the queues they manage. Each task can contain any JSON 12 | serializable python objects. We use `JSON` internally to store the tasks 13 | in the queues. 14 | 15 | Workers can execute tasks and return the result asynchronously and if required 16 | synchronously (wait until worker returns the result to the job). 17 | 18 | Dependencies 19 | ------------ 20 | - python-redis 21 | - mock (for tests) 22 | - A running Redis server 23 | 24 | Testimonial(s) 25 | -------------- 26 | 27 | **Ralph Bean** 28 | python-retask made job distribution easy at a time when I didn't want to have 29 | to think about job distribution. Life saver. 30 | 31 | 32 | User Guide 33 | ---------- 34 | 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | 39 | user/intro 40 | user/redis 41 | user/install 42 | user/quickstart 43 | user/tutorials 44 | 45 | 46 | 47 | API Documentation 48 | ----------------- 49 | 50 | .. toctree:: 51 | :maxdepth: 2 52 | 53 | api 54 | 55 | Indices and tables 56 | ------------------ 57 | 58 | * :ref:`genindex` 59 | 60 | -------------------------------------------------------------------------------- /docs/retask.queue.rst: -------------------------------------------------------------------------------- 1 | :mod:`retask.queue` 2 | ======================= 3 | This module contains the primary :class:`Queue` which 4 | can be used to create and manage queues. 5 | 6 | .. autoclass:: retask.queue.Queue 7 | :members: 8 | 9 | .. autoclass:: retask.queue.Job 10 | :members: 11 | -------------------------------------------------------------------------------- /docs/retask.task.rst: -------------------------------------------------------------------------------- 1 | :mod:`retask.task` 2 | ======================= 3 | This module conatins generic task class, which can be used to create 4 | any kind of given task with serializable python objects as data. 5 | 6 | .. py:class:: retask.Task(data=None, raw=False) 7 | 8 | Returns a new Task object, the information for the task is passed through argument *data*. 9 | 10 | .. py:attribute:: data 11 | 12 | The python object containing information for the current task 13 | -------------------------------------------------------------------------------- /docs/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | This part of the documentation covers the installation of Retask. 7 | The first step to using any software package is getting it properly installed. 8 | 9 | 10 | Distribute & Pip 11 | ---------------- 12 | 13 | Installing requests is simple with `pip `_:: 14 | 15 | $ pip install retask 16 | 17 | or, with `easy_install `_:: 18 | 19 | $ easy_install retask 20 | 21 | But, you really `shouldn't do that `_. 22 | 23 | 24 | 25 | Get the Code 26 | ------------ 27 | 28 | Retask is actively developed on GitHub, where the code is 29 | `always available `_. 30 | 31 | You can either clone the public repository:: 32 | 33 | git clone git://github.com/kushaldas/retask.git 34 | 35 | Download the `tarball `_:: 36 | 37 | $ curl -OL https://github.com/kushaldas/retask/tarball/master 38 | 39 | Or, download the `zipball `_:: 40 | 41 | $ curl -OL https://github.com/kushaldas/retask/tarball/master 42 | 43 | 44 | Once you have a copy of the source, you can embed it in your Python package, 45 | or install it into your site-packages easily:: 46 | 47 | $ python setup.py install 48 | 49 | .. _redis: 50 | 51 | Installing redis-py 52 | ------------------- 53 | 54 | 55 | You can install ``redis-py`` with ``pip``:: 56 | 57 | $ pip install redis 58 | -------------------------------------------------------------------------------- /docs/user/intro.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introduction 4 | ============ 5 | 6 | First requirement 7 | ----------------- 8 | 9 | For various others projects I had to start looking for a 10 | simple task queue and solve kind of classical producer-consumer problems 11 | using them. 12 | 13 | This project started from that idea. 14 | 15 | 16 | Why Redis 17 | --------- 18 | 19 | I am following `Redis `_ development for a long time and using 20 | it in various other projects. The simplicity it provides and rich datastructures 21 | are always a plus to use it. 22 | 23 | Redis takes care of multithreading issues, it also helps to have data on disk for 24 | data persistence. It does not have any external dependencies and also very small 25 | in size, which helps to be used in enterprise world. 26 | 27 | Retask License 28 | -------------- 29 | 30 | Copyright (C) 2012, Kushal Das 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | Quickstart 4 | ========== 5 | 6 | For this example to work you should have your redis instance 7 | up and running. 8 | 9 | producer.py 10 | ----------- 11 | This code puts new task in the queue. We will have a dictionary as 12 | the information in this example. 13 | 14 | :: 15 | 16 | from retask import Task 17 | from retask import Queue 18 | queue = Queue('example') 19 | info1 = {'user':'kushal', 'url':'http://kushaldas.in'} 20 | info2 = {'user':'fedora planet', 'url':'http://planet.fedoraproject.org'} 21 | task1 = Task(info1) 22 | task2 = Task(info2) 23 | queue.connect() 24 | queue.enqueue(task1) 25 | queue.enqueue(task2) 26 | 27 | 28 | consumer.py 29 | ----------- 30 | This code gets the tasks from the queue. Based on the actual requirement, the 31 | client will work on the information it received as the task. For now we will 32 | just print the data. 33 | 34 | :: 35 | 36 | from retask import Task 37 | from retask import Queue 38 | queue = Queue('example') 39 | queue.connect() 40 | while queue.length != 0: 41 | task = queue.dequeue() 42 | if task: 43 | print(task.data) 44 | 45 | -------------------------------------------------------------------------------- /docs/user/redis.rst: -------------------------------------------------------------------------------- 1 | .. redissetup: 2 | 3 | Setting up the Redis Server 4 | =========================== 5 | You can download and install `Redis `_ on your distro. 6 | 7 | In `Fedora `_ you can just ``yum install redis`` 8 | for the same. 9 | 10 | To start the server in the local folder use the following command: 11 | 12 | :: 13 | 14 | $ redis-server 15 | 16 | On Fedora you can start the service as *root*: 17 | 18 | :: 19 | 20 | # systemctl enable redis.service 21 | # systemctl start redis.service 22 | 23 | In `Debian `_ just install the redis-server package with 24 | ``apt-get install redis-server`` to have a redis server running. 25 | 26 | -------------------------------------------------------------------------------- /docs/user/tutorials.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | Tutorials 4 | ========= 5 | 6 | This section of the document we have in depth examples of various use cases. 7 | 8 | Async data transfer between producer and worker 9 | ------------------------------------------------ 10 | In many real life scenarios we need to send the result back from the worker instances 11 | to the producer. The following code examples shows how to achieve that. 12 | 13 | async_producer.py 14 | ++++++++++++++++++ 15 | 16 | :: 17 | 18 | from retask import Task 19 | from retask import Queue 20 | import time 21 | queue = Queue('example') 22 | info1 = {'user': 'Fedora planet', 'url': 'http://planet.fedoraproject.org'} 23 | task1 = Task(info1) 24 | queue.connect() 25 | job = queue.enqueue(task1) 26 | print(job.result) 27 | time.sleep(30) 28 | print(job.result) 29 | 30 | 31 | Here queue.enqueue method returns a :class:`~retask.queue.Job` object. We can access job.result 32 | to see returned result from a worker. If there is no result yet came back from the worker, it will 33 | print `None`. If you don't need any returned data from the worker you can safely ignore the job object. 34 | 35 | async_consumer.py 36 | ++++++++++++++++++ 37 | 38 | :: 39 | 40 | from retask import Task 41 | from retask import Queue 42 | import time 43 | queue = Queue('example') 44 | queue.connect() 45 | task = queue.wait() 46 | print(task.data) 47 | time.sleep(15) 48 | queue.send(task, "We received your information dear %s" % task.data['user']) 49 | 50 | 51 | In the above example we see two newly introduced methods :class:`~retask.queue.Queue`. 52 | :func:`~retask.queue.Queue.wait` is a blocking call to wait for a new task in the queue. This is 53 | the preferred method over polling using :func:`~retask.queue.Queue.dequeue`. 54 | To send the result back workers will use :func:`~retask.queue.Queue.send` method, which takes an optional argument 55 | `wait_time` to specify timeout value in seconds. 56 | 57 | Synchronous / blocking wait for the result 58 | ------------------------------------------- 59 | 60 | :: 61 | 62 | from retask import Task 63 | from retask import Queue 64 | queue = Queue('example') 65 | info1 = {'user': 'Fedora planet', 'url': 'http://planet.fedoraproject.org'} 66 | task1 = Task(info1) 67 | queue.connect() 68 | job = queue.enqueue(task1) 69 | job.wait() 70 | print(job.result) 71 | 72 | In this example we are using :func:`~retask.queue.Job.wait` function to do a blocking 73 | synchronous call to the worker. 74 | -------------------------------------------------------------------------------- /examples/async_producer.py: -------------------------------------------------------------------------------- 1 | from retask import Task 2 | from retask import Queue 3 | import time 4 | queue = Queue('example') 5 | info1 = {'user': 'Fedora planet', 'url': 'http://planet.fedoraproject.org'} 6 | task1 = Task(info1) 7 | queue.connect() 8 | job = queue.enqueue(task1) 9 | print(job.result) 10 | time.sleep(30) 11 | print(job.result) 12 | -------------------------------------------------------------------------------- /examples/async_worker.py: -------------------------------------------------------------------------------- 1 | from retask import Queue 2 | import time 3 | queue = Queue('example') 4 | queue.connect() 5 | task = queue.wait() 6 | print(task.data) 7 | time.sleep(15) 8 | queue.send(task, "We received your information dear %s" % task.data['user']) 9 | -------------------------------------------------------------------------------- /examples/consumer.py: -------------------------------------------------------------------------------- 1 | from retask import Queue 2 | queue = Queue('example') 3 | queue.connect() 4 | while queue.length != 0: 5 | task = queue.dequeue() 6 | print(task.data) 7 | 8 | -------------------------------------------------------------------------------- /examples/producer.py: -------------------------------------------------------------------------------- 1 | from retask import Task 2 | from retask import Queue 3 | queue = Queue('example') 4 | info1 = {'user':'kushal', 'url':'http://kushaldas.in'} 5 | info2 = {'user':'fedora planet', 'url':'http://planet.fedoraproject.org'} 6 | task1 = Task(info1) 7 | task2 = Task(info2) 8 | queue.connect() 9 | queue.enqueue(task1) 10 | queue.enqueue(task2) 11 | 12 | -------------------------------------------------------------------------------- /examples/sync_producer.py: -------------------------------------------------------------------------------- 1 | from retask import Task 2 | from retask import Queue 3 | queue = Queue('example') 4 | info1 = {'user': 'Fedora planet', 'url': 'http://planet.fedoraproject.org'} 5 | task1 = Task(info1) 6 | queue.connect() 7 | job = queue.enqueue(task1) 8 | job.wait() 9 | print(job.result) 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis 2 | mock 3 | -------------------------------------------------------------------------------- /retask/__init__.py: -------------------------------------------------------------------------------- 1 | from .exceptions import (RetaskException, ConnectionError) 2 | from .task import Task 3 | from .queue import Queue, Job 4 | -------------------------------------------------------------------------------- /retask/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | retask.exceptions 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains the set of Retask's exceptions. 8 | 9 | """ 10 | 11 | class RetaskException(RuntimeError): 12 | """Some ambiguous exception occurred""" 13 | 14 | class ConnectionError(RetaskException): 15 | """A Connection error occurred.""" 16 | -------------------------------------------------------------------------------- /retask/queue.py: -------------------------------------------------------------------------------- 1 | #Copyright (C) 2012, Kushal Das 2 | 3 | #Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | #this software and associated documentation files (the "Software"), to deal in 5 | #the Software without restriction, including without limitation the rights to 6 | #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | #of the Software, and to permit persons to whom the Software is furnished to do 8 | #so, subject to the following conditions: 9 | 10 | #The above copyright notice and this permission notice shall be included in all 11 | #copies or substantial portions of the Software. 12 | 13 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | #SOFTWARE. 20 | 21 | __author__ = 'Kushal Das ' 22 | __copyright__ = 'Copyright (c) 2012-2016 Kushal Das' 23 | __license__ = 'MIT' 24 | __status__ = 'Production/Stable' 25 | __version__ = '1.0' 26 | 27 | """ 28 | retask Queue implementation 29 | 30 | """ 31 | import json 32 | import redis 33 | import uuid 34 | from .task import Task 35 | from .exceptions import ConnectionError 36 | 37 | KEY_PREFIX = 'retaskqueue-' 38 | 39 | class Queue(object): 40 | """ 41 | Returns the ``Queue`` object with the given name. If the user 42 | passes optional config dictionary with details for Redis 43 | server, it will connect to that instance. By default it connects 44 | to the localhost. 45 | 46 | """ 47 | def __init__(self, name, config=None): 48 | specified_config = config or {} 49 | self.name = name 50 | self._name = KEY_PREFIX + name 51 | self.config = { 52 | 'host': 'localhost', 53 | 'port': 6379, 54 | 'db': 0, 55 | 'password': None, 56 | } 57 | self.config.update(specified_config) 58 | self.rdb = None 59 | self.connected = False 60 | 61 | def names(self): 62 | """ 63 | Returns a list of queues available, ``None`` if no such 64 | queues found. Remember this will only shows queues with 65 | at least one item enqueued. 66 | """ 67 | data = None 68 | if not self.connected: 69 | raise ConnectionError('Queue is not connected') 70 | 71 | try: 72 | data = self.rdb.keys(KEY_PREFIX + '*') 73 | except redis.exceptions.ConnectionError as err: 74 | raise ConnectionError(str(err)) 75 | 76 | return [name.decode()[len(KEY_PREFIX):] for name in data] 77 | 78 | @property 79 | def length(self): 80 | """ 81 | Gives the length of the queue. Returns ``None`` if the queue is not 82 | connected. 83 | 84 | If the queue is not connected then it will raise 85 | :class:`retask.ConnectionError`. 86 | 87 | """ 88 | if not self.connected: 89 | raise ConnectionError('Queue is not connected') 90 | 91 | try: 92 | length = self.rdb.llen(self._name) 93 | except redis.exceptions.ConnectionError as err: 94 | raise ConnectionError(str(err)) 95 | 96 | return length 97 | 98 | def connect(self): 99 | """ 100 | Creates the connection with the redis server. 101 | Return ``True`` if the connection works, else returns 102 | ``False``. It does not take any arguments. 103 | 104 | :return: ``Boolean`` value 105 | 106 | .. note:: 107 | 108 | After creating the ``Queue`` object the user should call 109 | the ``connect`` method to create the connection. 110 | 111 | .. doctest:: 112 | 113 | >>> from retask import Queue 114 | >>> q = Queue('test') 115 | >>> q.connect() 116 | True 117 | 118 | """ 119 | config = self.config 120 | self.rdb = redis.Redis(config['host'], config['port'], config['db'],\ 121 | config['password']) 122 | try: 123 | info = self.rdb.info() 124 | self.connected = True 125 | except redis.ConnectionError: 126 | return False 127 | 128 | return True 129 | 130 | def wait(self, wait_time=0): 131 | """ 132 | Returns a :class:`~retask.task.Task` object from the queue. Returns ``False`` if it timeouts. 133 | 134 | :arg wait_time: Time in seconds to wait, default is infinite. 135 | 136 | :return: :class:`~retask.task.Task` object from the queue or False if it timeouts. 137 | 138 | .. doctest:: 139 | 140 | >>> from retask import Queue 141 | >>> q = Queue('test') 142 | >>> q.connect() 143 | True 144 | >>> task = q.wait() 145 | >>> print(task.data) 146 | {u'name': u'kushal'} 147 | 148 | .. note:: 149 | 150 | This is a blocking call, you can specity wait_time argument for timeout. 151 | 152 | """ 153 | if not self.connected: 154 | raise ConnectionError('Queue is not connected') 155 | 156 | data = self.rdb.brpop(self._name, wait_time) 157 | if data: 158 | task = Task() 159 | task.__dict__ = json.loads(data[1].decode()) 160 | return task 161 | else: 162 | return False 163 | 164 | def dequeue(self): 165 | """ 166 | Returns a :class:`~retask.task.Task` object from the queue. Returns ``None`` if the 167 | queue is empty. 168 | 169 | :return: :class:`~retask.task.Task` object from the queue 170 | 171 | If the queue is not connected then it will raise 172 | :class:`retask.ConnectionError` 173 | 174 | .. doctest:: 175 | 176 | >>> from retask import Queue 177 | >>> q = Queue('test') 178 | >>> q.connect() 179 | True 180 | >>> t = q.dequeue() 181 | >>> print(t.data) 182 | {u'name': u'kushal'} 183 | 184 | """ 185 | if not self.connected: 186 | raise ConnectionError('Queue is not connected') 187 | 188 | if self.rdb.llen(self._name) == 0: 189 | return None 190 | 191 | data = self.rdb.rpop(self._name) 192 | if not data: 193 | return None 194 | task = Task() 195 | task.__dict__ = json.loads(data.decode()) 196 | return task 197 | 198 | def enqueue(self, task): 199 | """ 200 | Enqueues the given :class:`~retask.task.Task` object to the queue and returns 201 | a :class:`~retask.queue.Job` object. 202 | 203 | :arg task: ::class:`~retask.task.Task` object 204 | 205 | :return: :class:`~retask.queue.Job` object 206 | 207 | If the queue is not connected then it will raise 208 | :class:`retask.ConnectionError`. 209 | 210 | .. doctest:: 211 | 212 | >>> from retask import Queue 213 | >>> q = Queue('test') 214 | >>> q.connect() 215 | True 216 | >>> from retask.task import Task 217 | >>> task = Task({'name':'kushal'}) 218 | >>> job = q.enqueue(task) 219 | 220 | """ 221 | if not self.connected: 222 | raise ConnectionError('Queue is not connected') 223 | 224 | try: 225 | #We can set the value to the queue 226 | job = Job(self.rdb) 227 | task.urn = job.urn 228 | text = json.dumps(task.__dict__) 229 | self.rdb.lpush(self._name, text) 230 | except Exception as err: 231 | return False 232 | return job 233 | 234 | def send(self, task, result, expire=60): 235 | """ 236 | Sends the result back to the producer. This should be called if only you 237 | want to return the result in async manner. 238 | 239 | :arg task: ::class:`~retask.task.Task` object 240 | :arg result: Result data to be send back. Should be in JSON serializable. 241 | :arg expire: Time in seconds after the key expires. Default is 60 seconds. 242 | """ 243 | self.rdb.lpush(task.urn, json.dumps(result)) 244 | self.rdb.expire(task.urn, expire) 245 | 246 | def __repr__(self): 247 | if not self: 248 | return '%s()' % (self.__class__.__name__,) 249 | return '%s(%r)' % (self.__class__.__name__, self.name) 250 | 251 | def find(self, obj): 252 | """Returns the index of the given object in the queue, it might be string 253 | which will be searched inside each task. 254 | 255 | :arg obj: object we are looking 256 | 257 | :return: -1 if the object is not found or else the location of the task 258 | """ 259 | if not self.connected: 260 | raise ConnectionError('Queue is not connected') 261 | 262 | data = self.rdb.lrange(self._name, 0, -1) 263 | for i, datum in enumerate(data): 264 | if datum.find(str(obj)) != -1: 265 | return i 266 | return -1 267 | 268 | 269 | class Job(object): 270 | """ 271 | Job object containing the result from the workers. 272 | 273 | :arg rdb: The underlying redis connection. 274 | """ 275 | def __init__(self, rdb): 276 | self.rdb = rdb 277 | self.urn = uuid.uuid4().urn 278 | self.__result = None 279 | 280 | @property 281 | def result(self): 282 | """ 283 | Returns the result from the worker for this job. This is used to pass 284 | result in async way. 285 | """ 286 | if self.__result: 287 | return self.__result 288 | data = self.rdb.rpop(self.urn) 289 | if data: 290 | self.rdb.delete(self.urn) 291 | data = json.loads(data.decode()) 292 | self.__result = data 293 | return data 294 | else: 295 | return None 296 | 297 | def wait(self, wait_time=0): 298 | """ 299 | Blocking call to check if the worker returns the result. One can use 300 | job.result after this call returns ``True``. 301 | 302 | :arg wait_time: Time in seconds to wait, default is infinite. 303 | 304 | :return: `True` or `False`. 305 | 306 | .. note:: 307 | 308 | This is a blocking call, you can specity wait_time argument for timeout. 309 | 310 | """ 311 | if self.__result: 312 | return True 313 | data = self.rdb.brpop(self.urn, wait_time) 314 | if data: 315 | self.rdb.delete(self.urn) 316 | data = json.loads(data[1].decode()) 317 | self.__result = data 318 | return True 319 | else: 320 | return False 321 | -------------------------------------------------------------------------------- /retask/task.py: -------------------------------------------------------------------------------- 1 | #Copyright (C) 2012, Kushal Das 2 | 3 | #Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | #this software and associated documentation files (the "Software"), to deal in 5 | #the Software without restriction, including without limitation the rights to 6 | #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | #of the Software, and to permit persons to whom the Software is furnished to do 8 | #so, subject to the following conditions: 9 | 10 | #The above copyright notice and this permission notice shall be included in all 11 | #copies or substantial portions of the Software. 12 | 13 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | #SOFTWARE. 20 | 21 | __author__ = 'Kushal Das ' 22 | __copyright__ = 'Copyright (c) 2012-2016 Kushal Das' 23 | __license__ = 'MIT' 24 | __status__ = 'Production/Stable' 25 | __version__ = '1.0' 26 | 27 | """ 28 | Task Class 29 | """ 30 | import json 31 | 32 | 33 | class Task(object): 34 | """ 35 | Returns a new Task object, the information for the task is passed through 36 | argument ``data`` 37 | 38 | :kwarg data: Python object which contains information for the task. Should be serializable through ``JSON``. 39 | 40 | """ 41 | 42 | def __init__(self, data=None, raw=False, urn=None): 43 | if not raw: 44 | self._data = json.dumps(data) 45 | else: 46 | self._data = data 47 | self.urn = urn 48 | 49 | @property 50 | def data(self): 51 | """ 52 | The python object containing information for the current task 53 | 54 | """ 55 | return json.loads(self._data) 56 | 57 | @property 58 | def rawdata(self): 59 | """ 60 | The string representation of the actual python objects for the task 61 | 62 | .. note:: 63 | This should not be used directly by the users. This is for internal use 64 | only. 65 | 66 | """ 67 | return self._data 68 | 69 | def __repr__(self): 70 | return '%s(%s)' % (self.__class__.__name__, repr(self.data)) 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from setuptools import find_packages, setup 6 | 7 | setup(name='retask', 8 | version='1.0', 9 | description='Task Queue implementation in python', 10 | long_description=( 11 | 'Retask is a simple task queue implementation written for ' 12 | 'human beings. It provides generic solution to create and manage ' 13 | 'task queues.' 14 | ), 15 | author='Kushal Das', 16 | author_email='kushaldas@gmail.com', 17 | maintainer='Kushal Das', 18 | maintainer_email='kushaldas@gmail.com', 19 | license='MIT', 20 | url='https://github.com/kushaldas/retask', 21 | classifiers=[ 22 | 'Development Status :: 6 - Mature', 23 | 'Topic :: Software Development :: Libraries', 24 | 'License :: OSI Approved :: MIT License', 25 | 'Topic :: System :: Distributed Computing', 26 | 'Programming Language :: Python :: 2.6', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3.4', 29 | 'Programming Language :: Python :: 3.5', 30 | ], 31 | packages=find_packages(), 32 | data_files=[], 33 | install_requires=[ 34 | 'redis' 35 | ], 36 | test_suite='tests', 37 | tests_require=[ 38 | 'mock' 39 | ]) 40 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import redis 3 | from mock import patch 4 | from retask import Task 5 | from retask import Queue 6 | 7 | 8 | class ConnectTest(unittest.TestCase): 9 | """ 10 | Test the connect method 11 | """ 12 | def runTest(self): 13 | queue = Queue('testqueue') 14 | self.assertTrue(queue.connect()) 15 | 16 | 17 | 18 | class LengthTest(unittest.TestCase): 19 | """ 20 | Tests the length method of the Queue 21 | 22 | """ 23 | @patch('redis.Redis') 24 | def runTest(self, mock_redis): 25 | m = mock_redis.return_value 26 | m.llen.return_value = 2 27 | queue = Queue('testqueue') 28 | queue.connect() 29 | self.assertEqual(queue.length, 2) 30 | 31 | 32 | class SetTest(unittest.TestCase): 33 | """ 34 | Sets a task in the Queue 35 | 36 | """ 37 | def runTest(self): 38 | queue = Queue('testqueue') 39 | queue.connect() 40 | t = Task({'name':'kushal'}) 41 | self.assertTrue(queue.enqueue(t)) 42 | 43 | def tearDown(self): 44 | rdb = redis.Redis() 45 | rdb.delete('retaskqueue-testqueue') 46 | 47 | 48 | class GetTest(unittest.TestCase): 49 | """ 50 | Gets a task in the Queue 51 | 52 | """ 53 | def setUp(self): 54 | queue = Queue('testqueue') 55 | queue.connect() 56 | t = Task({'name':'kushal'}) 57 | queue.enqueue(t) 58 | 59 | 60 | def runTest(self): 61 | queue = Queue('testqueue') 62 | queue.connect() 63 | task = queue.dequeue() 64 | i = task.data 65 | self.assertEqual(task.data['name'], 'kushal') 66 | 67 | class GetQueueNamesTest(unittest.TestCase): 68 | """ 69 | Gets a task in the Queue 70 | 71 | """ 72 | def setUp(self): 73 | queue = Queue('lambda') 74 | queue.connect() 75 | t = Task({'name':'kushal'}) 76 | queue.enqueue(t) 77 | 78 | def runTest(self): 79 | queue = Queue('lambda') 80 | queue.connect() 81 | results = queue.names() 82 | self.assertEqual(results[0], 'lambda') 83 | 84 | def tearDown(self): 85 | rdb = redis.Redis() 86 | rdb.delete('retaskqueue-lambda') 87 | 88 | if __name__ == '__main__': 89 | unittest.main() 90 | 91 | 92 | --------------------------------------------------------------------------------