├── .ci ├── deploy_key.enc ├── travis-before-install.sh ├── travis-build-docs.sh ├── travis-install.sh ├── travis-release.sh └── travis-tests.sh ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── asynctnt_queue ├── __init__.py ├── exceptions.py ├── queue.py ├── task.py └── tube.py ├── docs ├── Makefile ├── api.rst ├── conf.py ├── examples.rst ├── index.rst ├── installation.rst ├── make.bat └── requirements.txt ├── requirements.txt ├── run_tests.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── files └── app.lua ├── test_queue.py ├── test_task.py └── test_tube.py /.ci/deploy_key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorcoding/asynctnt-queue/75719b2dd27e8314ae924aea6a7a85be8f48ecc5/.ci/deploy_key.enc -------------------------------------------------------------------------------- /.ci/travis-before-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "TRAVIS_OS_NAME = ${TRAVIS_OS_NAME}" 4 | 5 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 6 | curl -L https://packagecloud.io/tarantool/${TARANTOOL_VERSION}/gpgkey | sudo apt-key add - 7 | release=`lsb_release -c -s` 8 | sudo apt-get -y install apt-transport-https 9 | sudo rm -f /etc/apt/sources.list.d/*tarantool*.list 10 | echo "deb https://packagecloud.io/tarantool/${TARANTOOL_VERSION}/ubuntu/ $release main" | sudo tee /etc/apt/sources.list.d/tarantool_${TARANTOOL_VERSION}.list 11 | echo "deb-src https://packagecloud.io/tarantool/${TARANTOOL_VERSION}/ubuntu/ $release main" | sudo tee -a /etc/apt/sources.list.d/tarantool_${TARANTOOL_VERSION}.list 12 | sudo apt-get -qq update 13 | sudo apt-get -y install tarantool 14 | sudo tarantoolctl stop example 15 | sudo apt-get install pandoc 16 | sudo apt-get install lua5.1 luarocks 17 | sudo luarocks install https://raw.githubusercontent.com/tarantool/rocks/gh-pages/queue-scm-1.rockspec 18 | 19 | elif [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then 20 | brew update 21 | if [[ "${TARANTOOL_VERSION}" == "1_7" ]]; then 22 | brew install tarantool --HEAD 23 | else 24 | brew install tarantool 25 | fi 26 | 27 | brew install lua@5.1 28 | luarocks-5.1 install https://raw.githubusercontent.com/tarantool/rocks/gh-pages/queue-scm-1.rockspec 29 | fi 30 | -------------------------------------------------------------------------------- /.ci/travis-build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | 6 | if [[ "${BUILD}" != *docs* ]]; then 7 | echo "Skipping docs build." 8 | exit 0 9 | fi 10 | 11 | 12 | SOURCE_BRANCH="master" 13 | TARGET_BRANCH="gh-pages" 14 | shopt -s dotglob 15 | 16 | function doCompile { 17 | pip install -r docs/requirements.txt 18 | make docs 19 | mkdir -p out 20 | mv docs/_build/html/* out/ 21 | } 22 | 23 | # Pull requests and commits to other branches shouldn't try to deploy, just build to verify 24 | if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then 25 | echo "Skipping deploy; just doing a build." 26 | doCompile 27 | exit 0 28 | fi 29 | 30 | # Save some useful information 31 | REPO=`git config remote.origin.url` 32 | SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:} 33 | SHA=`git rev-parse --verify HEAD` 34 | 35 | # Clone the existing gh-pages for this repo into out/ 36 | # Create a new empty branch if gh-pages doesn't exist yet 37 | # (should only happen on first deploy) 38 | git clone $REPO out 39 | cd out 40 | git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH 41 | cd .. 42 | 43 | # Clean out existing contents 44 | cd out 45 | find -not -path "./.git/*" -not -name ".git" -delete 46 | cd .. 47 | 48 | # Run our compile script 49 | doCompile 50 | 51 | # Now let's go have some fun with the cloned repo 52 | cd out 53 | git config user.name "Travis CI" 54 | git config user.email "$COMMIT_AUTHOR_EMAIL" 55 | 56 | # If there are no changes to the compiled out (e.g. this is a README update) then just bail. 57 | if [ -z `git diff --exit-code` ]; then 58 | echo "No changes to the output on this push; exiting." 59 | exit 0 60 | fi 61 | 62 | # Commit the "changes", i.e. the new version. 63 | # The delta will show diffs between new and old versions. 64 | git add . 65 | git commit -m "Deploy to GitHub Pages: ${SHA}" 66 | 67 | # Get the deploy key by using Travis's stored variables to decrypt deploy_key.enc 68 | cd .. 69 | set +x 70 | ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" 71 | ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" 72 | ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR} 73 | ENCRYPTED_IV=${!ENCRYPTED_IV_VAR} 74 | openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in .ci/deploy_key.enc -out deploy_key -d 75 | set -x 76 | chmod 600 deploy_key 77 | eval `ssh-agent -s` 78 | ssh-add deploy_key 79 | 80 | # Now that we're all set up, we can push. 81 | cd out 82 | git push $SSH_REPO $TARGET_BRANCH 83 | -------------------------------------------------------------------------------- /.ci/travis-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | if [ "${TRAVIS_OS_NAME}" == "linux" ]; then 6 | echo "linux" 7 | elif [ "${TRAVIS_OS_NAME}" == "osx" ]; then 8 | git clone https://github.com/yyuu/pyenv.git ~/.pyenv 9 | PYENV_ROOT="$HOME/.pyenv" 10 | PATH="$PYENV_ROOT/bin:$PATH" 11 | eval "$(pyenv init -)" 12 | 13 | if ! (pyenv versions | grep "${PYTHON_VERSION}$"); then 14 | pyenv install ${PYTHON_VERSION} 15 | fi 16 | pyenv global ${PYTHON_VERSION} 17 | pyenv rehash 18 | fi 19 | 20 | pip install --upgrade pip wheel 21 | pip install --upgrade setuptools 22 | pip install -r requirements.txt 23 | pip install coveralls 24 | pip install -e . 25 | -------------------------------------------------------------------------------- /.ci/travis-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | 6 | if [ -z "${TRAVIS_TAG}" ]; then 7 | # Not a release 8 | exit 0 9 | fi 10 | 11 | 12 | python setup.py sdist 13 | twine upload dist/* 14 | -------------------------------------------------------------------------------- /.ci/travis-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ "${BUILD}" != *tests* ]]; then 4 | echo "Skipping tests." 5 | exit 0 6 | fi 7 | 8 | if [ "${TRAVIS_OS_NAME}" == "osx" ]; then 9 | PYENV_ROOT="$HOME/.pyenv" 10 | PATH="$PYENV_ROOT/bin:$PATH" 11 | eval "$(pyenv init -)" 12 | fi 13 | 14 | if [[ "${BUILD}" == *quicktests* ]]; then 15 | make && make quicktest || exit 1 16 | else 17 | make && make test || exit 1 18 | make clean && make debug && make test || exit 1 19 | fi 20 | 21 | if [[ "${BUILD}" == *coverage* ]]; then 22 | make debug && coverage run --source=asynctnt_queue setup.py test 23 | fi 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | .venv/ 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | # IDEA 95 | .idea 96 | 97 | # Custom 98 | *.snap 99 | *.xlog 100 | *.xctl 101 | *.c 102 | asynctnt/iproto/*.html 103 | __tnt* 104 | vinyl.meta 105 | *.vymeta 106 | deploy_key* 107 | !.ci/deploy_key.enc 108 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | env: 3 | global: 4 | - ENCRYPTION_LABEL: "ece81675fc86" 5 | - COMMIT_AUTHOR_EMAIL: "igorcoding@gmail.com" 6 | 7 | matrix: 8 | fast_finish: true 9 | include: 10 | 11 | # osx 12 | - os: osx 13 | language: generic 14 | env: BUILD=tests PYTHON_VERSION=3.6.0 TARANTOOL_VERSION=1_6 15 | 16 | - os: osx 17 | language: generic 18 | env: BUILD=tests PYTHON_VERSION=3.6.0 TARANTOOL_VERSION=1_7 19 | 20 | # precise python3.5 Tarantool 1.6 21 | - os: linux 22 | dist: precise 23 | sudo: required 24 | language: python 25 | python: '3.5' 26 | env: BUILD=quicktests TARANTOOL_VERSION=1_6 27 | 28 | # trusty python3.5 Tarantool 1.6 29 | - os: linux 30 | dist: trusty 31 | sudo: required 32 | language: python 33 | python: '3.5' 34 | env: BUILD=quicktests TARANTOOL_VERSION=1_6 35 | 36 | # trusty python3.5 Tarantool 1.7 37 | - os: linux 38 | dist: trusty 39 | sudo: required 40 | language: python 41 | python: '3.5' 42 | env: BUILD=tests TARANTOOL_VERSION=1_7 43 | 44 | # trusty python3.6 Tarantool 1.8 45 | - os: linux 46 | dist: trusty 47 | sudo: required 48 | language: python 49 | python: '3.6' 50 | env: BUILD=tests TARANTOOL_VERSION=1_8 51 | 52 | # trusty python3.6 Tarantool 1.7 + deploy 53 | - os: linux 54 | dist: trusty 55 | sudo: required 56 | language: python 57 | python: '3.6' 58 | env: BUILD=tests,docs,coverage,release TARANTOOL_VERSION=1_7 59 | 60 | cache: 61 | - pip 62 | 63 | before_install: 64 | - ".ci/travis-before-install.sh" 65 | 66 | install: 67 | - ".ci/travis-install.sh" 68 | 69 | script: 70 | - ".ci/travis-tests.sh" 71 | - ".ci/travis-build-docs.sh" 72 | 73 | after_success: 74 | - if [[ "${BUILD}" == *coverage* ]]; then coveralls; fi 75 | 76 | deploy: 77 | - provider: releases 78 | skip_cleanup: true 79 | api_key: 80 | secure: CJ6KqhcxNx9seTTk5bXyoEC4tyQu1stquP2E1DWn7F5rhRmuR5ApKuJfgVOZDcy3S59jTuAperW/He3CHghkHcm7JspB8270zh/rwxFx6Viw3hPXsJFAB4GbBKXSRxc6XTTa0Jh3cC3yIwNSfm2BbcJb6HiLnyeqOjoHs83fZYVK/eFeWQQc9VGSdsop7urOQC7iKLM0BXIivOVuuFGJPSCz2VvvDpx/CKbr1qJ3qHbkCJhze/D+IAudbvEmgO1IhFEQHe7oo+cBN1aTgrbZqFM55V5WH48s/CWlVb3SE+RISiPLN3fY9edbi4UKm1SFz43LQ6BNG77fttdN4sKXz7zy5/73mENt1oavhx0l0tnIMUgoEPCb3rUD4BgJaFcSAFdiX1Kv5TU5A5Yu/BsrRdaScwdDw+vpvdQCoucFBn2lXVotyOHT/fFKiEjQ6WRRxMnaukvQcIhoLn4N+jHEh2txFTyDE5iMeXDuqf730xTjwC34Bixru0NANzJsT+MJBZTND7rWA19M6TLxHcAeDgePYVdfgMZt+944eN/qeQcYgDSaKM5KGW3pibG8M4hhqfr1Zcm0LI8kZA9zVV9fahK2Gt6UIbMI/1PzzVLRYF7lFYfePsmToj6QoRWmn5+hQ8EfuJwsiF9nQ+cr1WzpcH5ccsB2KDfIzwWF6L2ThF4= 81 | file_glob: true 82 | file: dist/asynctnt-queue*.tar.gz 83 | on: 84 | repo: igorcoding/asynctnt-queue 85 | tags: true 86 | condition: '"${BUILD}" == *release*' 87 | 88 | - provider: script 89 | script: .ci/travis-release.sh 90 | on: 91 | tags: true 92 | condition: '"${BUILD}" == *release*' 93 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tests *.py 2 | recursive-include asynctnt_queue *.py 3 | include LICENSE.txt README.md 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build debug test coverage clean annotate all 2 | 3 | 4 | PYTHON ?= python 5 | 6 | 7 | all: build 8 | 9 | 10 | clean: 11 | rm -rf dist build *.egg-info 12 | find . -name '__pycache__' | xargs rm -rf 13 | rm -rf htmlcov 14 | rm -rf __tnt* 15 | 16 | 17 | build: 18 | $(PYTHON) setup.py build 19 | 20 | 21 | test: 22 | PYTHONASYNCIODEBUG=1 $(PYTHON) -m unittest discover -v -s tests 23 | $(PYTHON) -m unittest discover -v -s tests 24 | USE_UVLOOP=1 $(PYTHON) -m unittest discover -v -s tests 25 | 26 | 27 | quicktest: 28 | $(PYTHON) -m unittest discover -v -s tests 29 | 30 | 31 | test_16: 32 | TARANTOOL_DOCKER_VERSION=1.6 $(PYTHON) -m unittest discover -s tests 33 | 34 | 35 | test_17: 36 | TARANTOOL_DOCKER_VERSION=1.7 $(PYTHON) -m unittest discover -s tests 37 | 38 | 39 | coverage: 40 | # pip install -e . 41 | coverage run run_tests.py 42 | coverage report -m 43 | coverage html 44 | 45 | 46 | style: 47 | pep8 asynctnt_queue 48 | pep8 tests 49 | 50 | 51 | sdist: clean build test 52 | $(PYTHON) setup.py sdist 53 | 54 | 55 | release: clean build test 56 | $(PYTHON) setup.py sdist upload 57 | 58 | 59 | docs: build 60 | $(MAKE) -C docs html 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asynctnt-queue 2 | 3 | [![Build Status](https://travis-ci.org/igorcoding/asynctnt-queue.svg?branch=master)](https://travis-ci.org/igorcoding/asynctnt-queue) 4 | [![Coverage Status](https://coveralls.io/repos/github/igorcoding/asynctnt-queue/badge.svg?branch=master)](https://coveralls.io/github/igorcoding/asynctnt-queue?branch=master) 5 | [![PyPI](https://img.shields.io/pypi/v/asynctnt-queue.svg)](https://pypi.python.org/pypi/asynctnt-queue) 6 | 7 | 8 | asynctnt-queue is a python/asyncio bindings library for 9 | [tarantool-queue](https://github.com/tarantool/queue) package in 10 | [Tarantool Database](https://tarantool.org/), integrated with [asynctnt](https://github.com/igorcoding/asynctnt) module. 11 | 12 | 13 | ## Documentation 14 | 15 | Documentation is available [here](https://igorcoding.github.io/asynctnt-queue). 16 | 17 | 18 | ## Installation 19 | Use pip to install: 20 | ```bash 21 | $ pip install asynctnt-queue 22 | ``` 23 | 24 | ## Basic Usage 25 | 26 | Tarantool config: 27 | 28 | ```lua 29 | box.cfg { 30 | listen = '127.0.0.1:3301' 31 | } 32 | 33 | box.once('v1', function() 34 | box.schema.user.grant('guest', 'read,write,execute', 'universe') 35 | end) 36 | 37 | queue = require('queue') 38 | queue.create_tube('test_tube', 'fifottl') 39 | ``` 40 | 41 | Python code: 42 | ```python 43 | import asyncio 44 | import asynctnt 45 | import asynctnt_queue 46 | 47 | 48 | async def run(): 49 | conn = asynctnt.Connection(host='127.0.0.1', port=3301) 50 | await conn.connect() 51 | 52 | queue = asynctnt_queue.Queue(conn) 53 | test_tube = queue.tube('test_tube') 54 | 55 | # Add a task to queue 56 | task = await test_tube.put({ 57 | 'key': 'value' 58 | }) 59 | 60 | print('Task id: {}'.format(task.task_id)) 61 | print('Task status: {}'.format(task.status)) 62 | 63 | # Retrieve a task from queue 64 | task = await test_tube.take(1) 65 | 66 | # ... do some work with task 67 | 68 | await task.ack() 69 | await conn.disconnect() 70 | 71 | loop = asyncio.get_event_loop() 72 | loop.run_until_complete(run()) 73 | ``` 74 | 75 | 76 | ## References 77 | 78 | 1. [Tarantool](https://tarantool.org) - in-memory database and application server. 79 | 2. [asynctnt](https://github.com/igorcoding/asynctnt) - fast Tarantool database connector for Python/asyncio 80 | 3. [aiotarantool](https://github.com/shveenkov/aiotarantool) - alternative Python/asyncio connector 81 | 82 | 83 | -------------------------------------------------------------------------------- /asynctnt_queue/__init__.py: -------------------------------------------------------------------------------- 1 | from .queue import Queue 2 | from .tube import Tube 3 | from .task import Task 4 | 5 | 6 | __version__ = '0.0.5' 7 | -------------------------------------------------------------------------------- /asynctnt_queue/exceptions.py: -------------------------------------------------------------------------------- 1 | class QueueError(Exception): 2 | """ 3 | Base QueueError exception 4 | """ 5 | pass 6 | 7 | 8 | class TaskEmptyError(QueueError): 9 | """ 10 | Raised when Tarantool responds with empty body for task 11 | """ 12 | pass 13 | -------------------------------------------------------------------------------- /asynctnt_queue/queue.py: -------------------------------------------------------------------------------- 1 | import asynctnt 2 | 3 | from .tube import Tube 4 | 5 | __all__ = ( 6 | 'Queue', 7 | ) 8 | 9 | 10 | class Queue: 11 | __slots__ = ( 12 | '_conn', '_tube_cls', '_tubes', '_namespace' 13 | ) 14 | 15 | def __init__(self, 16 | conn: asynctnt.Connection, 17 | tube_cls=Tube, 18 | namespace='queue'): 19 | """ 20 | Queue constructor. 21 | 22 | :param conn: 23 | asynctnt connection (see 24 | `asynctnt `__ 25 | documentation) 26 | :param tube_cls: 27 | Tube class that is used for Tube creation (default is 28 | :class:`asynctnt_queue.Tube`) 29 | :param namespace: 30 | Variable which was used for queue module import ( 31 | default is `queue`) 32 | """ 33 | assert isinstance(conn, asynctnt.Connection), \ 34 | 'conn must be asynctnt.Connection instance' 35 | self._conn = conn 36 | self._tube_cls = tube_cls 37 | self._tubes = {} 38 | self._namespace = namespace 39 | 40 | @property 41 | def conn(self): 42 | """ 43 | ``asynctnt`` connection 44 | 45 | :returns: :class:`asynctnt.Connection` instance 46 | """ 47 | return self._conn 48 | 49 | @property 50 | def namespace(self): 51 | """ 52 | Queues namespace 53 | 54 | :returns: :class:`str` instance 55 | """ 56 | return self._namespace 57 | 58 | def tube(self, name): 59 | """ 60 | Returns tube by its name 61 | 62 | :param name: Tube name 63 | :returns: ``self.tube_cls`` instance 64 | (by default :class:`asynctnt_queue.Tube`) 65 | """ 66 | if name in self._tubes: 67 | return self._tubes[name] 68 | 69 | assert name, 'Tube name must be specified' 70 | t = self._tube_cls(self, name) 71 | self._tubes[name] = t 72 | return t 73 | 74 | async def statistics(self, tube_name=None): 75 | """ 76 | Returns queue statistics (coroutine) 77 | 78 | :param tube_name: 79 | If specified, statistics by a specific tube is returned, 80 | else statistics about all tubes is returned 81 | """ 82 | args = None 83 | if tube_name is not None: 84 | args = (tube_name,) 85 | 86 | res = await self._conn.call('{}.statistics'.format(self._namespace), args) 87 | if self._conn.version < (1, 7): # pragma: nocover 88 | return res.body[0][0] 89 | return res.body[0] 90 | -------------------------------------------------------------------------------- /asynctnt_queue/task.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | import asynctnt.log 4 | 5 | from .exceptions import TaskEmptyError 6 | 7 | 8 | __all__ = ( 9 | 'Status', 'Task' 10 | ) 11 | 12 | 13 | class Status(enum.Enum): 14 | READY = 'r' 15 | TAKEN = 't' 16 | EXECUTED = '-' 17 | BURIED = '!' 18 | DELAYED = '~' 19 | 20 | 21 | class Task: 22 | __slots__ = ( 23 | '_tube', '_task_id', '_status', '_data' 24 | ) 25 | 26 | def __init__(self, tube, tnt_tuple): 27 | self._tube = tube 28 | if tnt_tuple is None or len(tnt_tuple) == 0: # pragma: nocover 29 | raise TaskEmptyError('Tarantool Queue task is empty') 30 | 31 | tnt_tuple = tnt_tuple[0] 32 | if tnt_tuple is None or len(tnt_tuple) == 0: # pragma: nocover 33 | raise TaskEmptyError('Tarantool Queue task is empty') 34 | 35 | self._task_id = tnt_tuple[0] 36 | status = tnt_tuple[1] 37 | try: 38 | self._status = Status(status) 39 | except ValueError: # pragma: nocover 40 | asynctnt.log.logger.warning( 41 | "unknown status '{}' in task_id = {}".format( 42 | status, self._task_id)) 43 | self._status = status 44 | self._data = tnt_tuple[2] 45 | 46 | @property 47 | def tube(self): 48 | """ 49 | Task's tube 50 | """ 51 | return self._tube 52 | 53 | @property 54 | def task_id(self): 55 | """ 56 | Task id 57 | """ 58 | return self._task_id 59 | 60 | @property 61 | def status(self): 62 | """ 63 | Task status 64 | 65 | :returns: :class:`asynctnt_queue.Status` instance 66 | """ 67 | return self._status 68 | 69 | @property 70 | def data(self): 71 | """ 72 | Task data 73 | """ 74 | return self._data 75 | 76 | def __repr__(self): 77 | return ''.format(self._task_id, self._status) 78 | 79 | async def touch(self, increment): 80 | """ 81 | Update task ttl and/or ttr by increment value 82 | 83 | :param increment: Seconds to add to ttr 84 | :return: Task instance 85 | """ 86 | return await self._tube.touch(self._task_id, increment) 87 | 88 | async def ack(self): 89 | """ 90 | Ack task 91 | 92 | :return: Task instance 93 | """ 94 | return await self._tube.ack(self._task_id) 95 | 96 | async def release(self, *, delay=None): 97 | """ 98 | Release task (return to queue) with delay if specified 99 | 100 | :param delay: Time in seconds before task will become ready again 101 | :return: Task instance 102 | """ 103 | return await self._tube.release(self._task_id, delay=delay) 104 | 105 | async def peek(self): 106 | """ 107 | Get task without changing its state 108 | 109 | :return: Task instance 110 | """ 111 | return await self._tube.peek(self._task_id) 112 | 113 | async def bury(self): 114 | """ 115 | Buries (disables) task 116 | 117 | :return: Task instance 118 | """ 119 | return await self._tube.bury(self._task_id) 120 | 121 | async def delete(self): 122 | """ 123 | Deletes task from queue 124 | 125 | :return: Task instance 126 | """ 127 | return await self._tube.delete(self._task_id) 128 | -------------------------------------------------------------------------------- /asynctnt_queue/tube.py: -------------------------------------------------------------------------------- 1 | from .task import Task 2 | 3 | 4 | __all__ = ( 5 | 'Tube', 6 | ) 7 | 8 | 9 | _FUNCS = [ 10 | 'put', 11 | 'take', 12 | 'touch', 13 | 'ack', 14 | 'release', 15 | 'peek', 16 | 'bury', 17 | 'kick', 18 | 'delete', 19 | 'drop', 20 | ] 21 | 22 | 23 | def build_func_name(namespace, tube_name, func_name): 24 | return '{}.tube.{}:{}'.format(namespace, tube_name, func_name) 25 | 26 | 27 | class Tube: 28 | __slots__ = ( 29 | '_queue', '_name', '__funcs' 30 | ) 31 | 32 | def __init__(self, queue, name): 33 | self._queue = queue 34 | self._name = name 35 | 36 | self.__funcs = {f: build_func_name(self._queue.namespace, self._name, f) for f in _FUNCS} 37 | 38 | @property 39 | def queue(self): 40 | """ 41 | Returns corresponding queue object 42 | 43 | :returns: :class:`asynctnt_queue.Queue` instance 44 | """ 45 | return self._queue 46 | 47 | @property 48 | def conn(self): 49 | """ 50 | Returns corresponding connection object 51 | 52 | :returns: :class:`asynctnt.Connection` instance 53 | """ 54 | return self._queue.conn 55 | 56 | @property 57 | def name(self): 58 | """ 59 | Tube name 60 | """ 61 | return self._name 62 | 63 | def _create_task(self, body, *, task_cls=Task): 64 | """ 65 | Creates Queue Task instance from Tarantool response body 66 | 67 | :param body: Response body 68 | :param task_cls: Class to instantiate 69 | :return: ``task_cls`` instance (by default 70 | :class:`asynctnt_queue.Task`) 71 | """ 72 | return task_cls(self, body) 73 | 74 | async def put(self, data, *, pri=None, ttl=None, ttr=None, delay=None): 75 | """ 76 | Puts data to the queue and returns a newly created Task 77 | 78 | :param data: Arbitrary task payload 79 | :param pri: Task priority (0 by default) 80 | :param ttl: Task time-to-live 81 | :param ttr: Task time-to-run 82 | :param delay: Task delay 83 | :return: Task instance 84 | """ 85 | opts = {} 86 | if pri is not None: 87 | opts['pri'] = pri 88 | 89 | if ttl is not None: 90 | opts['ttl'] = ttl 91 | 92 | if ttr is not None: 93 | opts['ttr'] = ttr 94 | 95 | if delay is not None: 96 | opts['delay'] = delay 97 | 98 | args = (data, opts) 99 | res = await self.conn.call(self.__funcs['put'], args) 100 | return self._create_task(res.body) 101 | 102 | async def take(self, timeout=None): 103 | """ 104 | Takes task from the queue, waiting the timeout if specified 105 | 106 | :param timeout: Seconds to wait for ready tasks 107 | :return: Task instance 108 | """ 109 | args = None 110 | if timeout is not None: 111 | args = (timeout,) 112 | 113 | res = await self.conn.call(self.__funcs['take'], args) 114 | if len(res.body) > 0: 115 | return self._create_task(res.body) 116 | return None 117 | 118 | async def touch(self, task_id, increment): 119 | """ 120 | Update task ttl and/or ttr by increment value 121 | 122 | :param task_id: Task id 123 | :param increment: Seconds to add to ttr 124 | :return: Task instance 125 | """ 126 | args = (task_id, increment) 127 | res = await self.conn.call(self.__funcs['touch'], args) 128 | return self._create_task(res.body) 129 | 130 | async def ack(self, task_id): 131 | """ 132 | Ack task 133 | 134 | :param task_id: Task id 135 | :return: Task instance 136 | """ 137 | args = (task_id,) 138 | res = await self.conn.call(self.__funcs['ack'], args) 139 | return self._create_task(res.body) 140 | 141 | async def release(self, task_id, *, delay=None): 142 | """ 143 | Release task (return to queue) with delay if specified 144 | 145 | :param task_id: Task id 146 | :param delay: Time in seconds before task will become ready again 147 | :return: Task instance 148 | """ 149 | opts = {} 150 | if delay is not None: 151 | opts['delay'] = delay 152 | args = (task_id, opts) 153 | res = await self.conn.call(self.__funcs['release'], args) 154 | return self._create_task(res.body) 155 | 156 | async def peek(self, task_id): 157 | """ 158 | Get task without changing its state 159 | 160 | :param task_id: Task id 161 | :return: Task instance 162 | """ 163 | 164 | args = (task_id,) 165 | res = await self.conn.call(self.__funcs['peek'], args) 166 | return self._create_task(res.body) 167 | 168 | async def bury(self, task_id): 169 | """ 170 | Buries (disables) task 171 | 172 | :param task_id: Task id 173 | :return: Task instance 174 | """ 175 | args = (task_id,) 176 | res = await self.conn.call(self.__funcs['bury'], args) 177 | return self._create_task(res.body) 178 | 179 | async def delete(self, task_id): 180 | """ 181 | Deletes task from queue 182 | 183 | :param task_id: Task id 184 | :return: Task instance 185 | """ 186 | args = (task_id,) 187 | res = await self.conn.call(self.__funcs['delete'], args) 188 | return self._create_task(res.body) 189 | 190 | async def kick(self, count): 191 | """ 192 | Kick `count` tasks from queue 193 | 194 | :param count: Tasks count to kick 195 | :return: Number of tasks actually kicked 196 | """ 197 | args = (count,) 198 | res = await self.conn.call(self.__funcs['kick'], args) 199 | if self.conn.version < (1, 7): 200 | return res.body[0][0] 201 | return res.body[0] 202 | 203 | def statistics(self): 204 | """ 205 | Return tube's statistics (identical to queue.statistics(tube_name)) 206 | 207 | :return: Tube's statistics 208 | """ 209 | return self._queue.statistics(self._name) 210 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = asynctnt_queue 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _asynctnt_queue-api: 2 | 3 | ============= 4 | API Reference 5 | ============= 6 | 7 | .. module:: asynctnt_queue 8 | :synopsis: Tarantool Queue python/asyncio bindings 9 | 10 | .. currentmodule:: asynctnt_queue 11 | 12 | Queue 13 | ===== 14 | 15 | .. autoclass:: asynctnt_queue.Queue 16 | :members: 17 | 18 | .. automethod:: __init__ 19 | 20 | 21 | Tube 22 | ==== 23 | 24 | .. autoclass:: asynctnt_queue.Tube 25 | :members: 26 | 27 | 28 | Task 29 | ==== 30 | 31 | .. autoclass:: asynctnt_queue.Task 32 | :members: 33 | 34 | 35 | 36 | Exceptions 37 | ========== 38 | 39 | .. autoclass:: asynctnt_queue.exceptions.QueueError 40 | :members: 41 | 42 | .. autoclass:: asynctnt_queue.exceptions.TaskEmptyError 43 | :members: 44 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # asynctnt documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Mar 12 18:57:44 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | 20 | import os 21 | import sys 22 | 23 | import sphinx_rtd_theme 24 | 25 | sys.path.insert(0, os.path.abspath('..')) 26 | 27 | 28 | def find_version(): 29 | import re 30 | for line in open("../asynctnt_queue/__init__.py"): 31 | if line.startswith("__version__"): 32 | return re.match( 33 | r"""__version__\s*=\s*(['"])([^'"]+)\1""", line).group(2) 34 | 35 | _ver = find_version() 36 | 37 | 38 | # -- General configuration ------------------------------------------------ 39 | 40 | extensions = ['sphinx.ext.autodoc', 41 | 'sphinx.ext.viewcode', 42 | 'sphinx.ext.githubpages', 43 | 'sphinxcontrib.asyncio'] 44 | 45 | templates_path = ['_templates'] 46 | source_suffix = '.rst' 47 | master_doc = 'index' 48 | 49 | project = 'asynctnt_queue' 50 | copyright = '2017, igorcoding' 51 | author = 'igorcoding' 52 | 53 | version = _ver 54 | release = _ver 55 | 56 | language = None 57 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 58 | pygments_style = 'sphinx' 59 | todo_include_todos = False 60 | 61 | 62 | # -- Options for HTML output ---------------------------------------------- 63 | 64 | html_theme = "sphinx_rtd_theme" 65 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 66 | html_static_path = ['_static'] 67 | 68 | 69 | # -- Options for HTMLHelp output ------------------------------------------ 70 | htmlhelp_basename = 'asynctnt_queue_doc' 71 | 72 | 73 | # -- Options for LaTeX output --------------------------------------------- 74 | 75 | latex_elements = { 76 | # The paper size ('letterpaper' or 'a4paper'). 77 | # 78 | # 'papersize': 'letterpaper', 79 | 80 | # The font size ('10pt', '11pt' or '12pt'). 81 | # 82 | # 'pointsize': '10pt', 83 | 84 | # Additional stuff for the LaTeX preamble. 85 | # 86 | # 'preamble': '', 87 | 88 | # Latex figure (float) alignment 89 | # 90 | # 'figure_align': 'htbp', 91 | } 92 | 93 | # Grouping the document tree into LaTeX files. List of tuples 94 | # (source start file, target name, title, 95 | # author, documentclass [howto, manual, or own class]). 96 | latex_documents = [ 97 | (master_doc, 'asynctnt_queue.tex', 'asynctnt_queue Documentation', 98 | 'igorcoding', 'manual'), 99 | ] 100 | 101 | 102 | # -- Options for manual page output --------------------------------------- 103 | 104 | # One entry per manual page. List of tuples 105 | # (source start file, name, description, authors, manual section). 106 | man_pages = [ 107 | (master_doc, 'asynctnt_queue', 'asynctnt_queue Documentation', 108 | [author], 1) 109 | ] 110 | 111 | 112 | # -- Options for Texinfo output ------------------------------------------- 113 | 114 | # Grouping the document tree into Texinfo files. List of tuples 115 | # (source start file, target name, title, author, 116 | # dir menu entry, description, category) 117 | texinfo_documents = [ 118 | (master_doc, 'asynctnt_queue', 'asynctnt_queue Documentation', 119 | author, 'asynctnt_queue', 'One line description of project.', 120 | 'Miscellaneous'), 121 | ] 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. _asynctnt_queue-examples: 2 | 3 | Examples 4 | ======== 5 | 6 | Basic Usage 7 | ----------- 8 | 9 | Tarantool config: 10 | 11 | .. code:: lua 12 | 13 | box.cfg { 14 | listen = '127.0.0.1:3301' 15 | } 16 | 17 | box.once('v1', function() 18 | box.schema.user.grant('guest', 'read,write,execute', 'universe') 19 | end) 20 | 21 | queue = require('queue') 22 | queue.create_tube('test_tube', 'fifottl') 23 | 24 | Python code: 25 | 26 | .. code:: python 27 | 28 | import asyncio 29 | import asynctnt 30 | import asynctnt_queue 31 | 32 | 33 | async def run(): 34 | conn = asynctnt.Connection(host='127.0.0.1', port=3301) 35 | await conn.connect() 36 | 37 | queue = asynctnt_queue.Queue(conn) 38 | test_tube = queue.tube('test_tube') 39 | 40 | # Add a task to queue 41 | task = await test_tube.put({ 42 | 'key': 'value' 43 | }) 44 | 45 | print('Task id: {}'.format(task.task_id)) 46 | print('Task status: {}'.format(task.status)) 47 | 48 | # Retrieve a task from queue 49 | task = await test_tube.take(1) 50 | 51 | # ... do some work with task 52 | 53 | await task.ack() 54 | await conn.disconnect() 55 | 56 | loop = asyncio.get_event_loop() 57 | loop.run_until_complete(run()) 58 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | asynctnt-queue 3 | ============== 4 | 5 | .. image:: https://travis-ci.org/igorcoding/asynctnt-queue.svg?branch=master 6 | :target: https://travis-ci.org/igorcoding/asynctnt-queue 7 | 8 | .. image:: https://img.shields.io/pypi/v/asynctnt-queue.svg 9 | :target: https://pypi.python.org/pypi/asynctnt-queue 10 | 11 | 12 | asynctnt-queue is a bindings library for 13 | `tarantool-queue `__ package in 14 | `Tarantool Database `__. It is integrated with 15 | `asynctnt `__ module 16 | 17 | 18 | License 19 | ------- 20 | asynctnt-queue is developed and distributed under the Apache 2.0 license. 21 | 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | :caption: Contents: 26 | 27 | installation 28 | examples 29 | api 30 | 31 | 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _asynctnt_queue-installation: 2 | 3 | 4 | Installation 5 | ============ 6 | 7 | To install asynctnt through **pip** run: 8 | 9 | .. code-block:: bash 10 | 11 | $ pip install asynctnt-queue 12 | 13 | 14 | Running tests 15 | ------------- 16 | 17 | To execute the testsuite simply run: 18 | 19 | .. code-block:: bash 20 | 21 | $ make test 22 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=asynctnt_queue 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | sphinx_rtd_theme 3 | sphinxcontrib-asyncio 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asynctnt>=0.1.9 2 | uvloop>=0.6.7 3 | pep8==1.7.0 4 | flake8==3.2.1 5 | pypandoc 6 | twine 7 | coverage 8 | pytest 9 | Sphinx 10 | sphinx_rtd_theme 11 | sphinxcontrib-asyncio 12 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | 5 | def discover_tests(): 6 | current_dir = os.path.dirname(os.path.abspath(__file__)) 7 | start_dir = os.path.join(current_dir, 'tests') 8 | 9 | loader = unittest.TestLoader() 10 | suite = loader.discover(start_dir, pattern='test_*.py') 11 | return suite 12 | 13 | 14 | def main(): 15 | suite = discover_tests() 16 | runner = unittest.TextTestRunner( 17 | verbosity=2 18 | ) 19 | runner.run(suite) 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import re 4 | 5 | try: 6 | from setuptools import setup 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | description = "Tarantool Queue python/asyncio bindings" 13 | try: 14 | import pypandoc 15 | long_description = pypandoc.convert('README.md', 'rst') 16 | except (IOError, ImportError): 17 | long_description = description 18 | 19 | 20 | def find_version(): 21 | for line in open("asynctnt_queue/__init__.py"): 22 | if line.startswith("__version__"): 23 | return re.match( 24 | r"""__version__\s*=\s*(['"])([^'"]+)\1""", line).group(2) 25 | 26 | 27 | setup( 28 | name="asynctnt-queue", 29 | packages=["asynctnt_queue"], 30 | include_package_data=True, 31 | version=find_version(), 32 | author="igorcoding", 33 | author_email="igorcoding@gmail.com", 34 | url='https://github.com/igorcoding/asynctnt-queue', 35 | license='Apache Software License', 36 | classifiers=[ 37 | "Programming Language :: Python :: 3 :: Only", 38 | "Programming Language :: Python :: 3.5", 39 | "Programming Language :: Python :: 3.6", 40 | "Intended Audience :: Developers", 41 | "License :: OSI Approved :: Apache Software License", 42 | "Topic :: Software Development :: Libraries :: Python Modules", 43 | "Topic :: Database :: Front-Ends" 44 | ], 45 | install_requires=[ 46 | 'asynctnt>=0.1.10' 47 | ], 48 | description=description, 49 | long_description=long_description, 50 | test_suite='run_tests.discover_tests' 51 | ) 52 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import sys 4 | 5 | import os 6 | from asynctnt._testbase import TarantoolTestCase 7 | 8 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | 11 | class BaseTarantoolTestCase(TarantoolTestCase): 12 | DO_CONNECT = True 13 | LOGGING_LEVEL = logging.CRITICAL 14 | LOGGING_STREAM = sys.stdout 15 | TNT_APP_LUA_PATH = os.path.join(CURRENT_DIR, 'files', 'app.lua') 16 | 17 | TESTER_SPACE_ID = 512 18 | TESTER_SPACE_NAME = 'tester' 19 | 20 | async def truncate(self): 21 | if self.conn.is_connected: 22 | await self.conn.call('truncate', timeout=5) 23 | 24 | def tearDown(self): 25 | if hasattr(self, 'conn'): 26 | self.loop.run_until_complete(self.truncate()) 27 | super().tearDown() 28 | -------------------------------------------------------------------------------- /tests/files/app.lua: -------------------------------------------------------------------------------- 1 | local function bootstrap() 2 | local b = { 3 | tarantool_ver = box.info.version, 4 | has_new_types = false, 5 | types = {} 6 | } 7 | 8 | if b.tarantool_ver >= "1.7.1-245" then 9 | b.has_new_types = true 10 | b.types.string = 'string' 11 | b.types.unsigned = 'unsigned' 12 | b.types.integer = 'integer' 13 | else 14 | b.types.string = 'str' 15 | b.types.unsigned = 'num' 16 | b.types.integer = 'int' 17 | end 18 | b.types.number = 'number' 19 | b.types.array = 'array' 20 | b.types.scalar = 'scalar' 21 | b.types.any = '*' 22 | return b 23 | end 24 | 25 | _G.B = bootstrap() 26 | 27 | 28 | box.once('v1', function() 29 | box.schema.user.create('t1', {password = 't1'}) 30 | box.schema.user.grant('t1', 'read,write,execute', 'universe') 31 | end) 32 | 33 | local queue = require('queue') 34 | _G.queue = queue 35 | 36 | queue.create_tube('test_tube', 'fifottl', {temporary = true}) 37 | 38 | 39 | function truncate() 40 | box.space.test_tube:truncate() 41 | end 42 | box.schema.func.create('truncate', {setuid=true}) 43 | -------------------------------------------------------------------------------- /tests/test_queue.py: -------------------------------------------------------------------------------- 1 | from asynctnt_queue import Queue, Tube 2 | from tests import BaseTarantoolTestCase 3 | 4 | 5 | class QueueTestCase(BaseTarantoolTestCase): 6 | async def test__queue_create(self): 7 | q = Queue(self.conn) 8 | self.assertEqual(q.conn, self.conn, 'conn valid') 9 | 10 | def test__queue_get_tube(self): 11 | q = Queue(self.conn) 12 | tube = q.tube('test_tube') 13 | 14 | self.assertEqual(tube.name, 'test_tube', 'name valid') 15 | self.assertIsInstance(tube, Tube, 'tube valid type') 16 | self.assertEqual(tube.conn, self.conn, 'conn valid') 17 | 18 | def test__queue_get_tube_multiple(self): 19 | q = Queue(self.conn) 20 | tube1 = q.tube('test_tube') 21 | tube2 = q.tube('test_tube') 22 | 23 | self.assertIs(tube1, tube2, 'the same object') 24 | 25 | async def test__queue_statistics(self): 26 | q = Queue(self.conn) 27 | res = await q.statistics() 28 | self.assertIsNotNone(res) 29 | self.assertIn('test_tube', res) 30 | -------------------------------------------------------------------------------- /tests/test_task.py: -------------------------------------------------------------------------------- 1 | from asynctnt_queue import Queue, Tube, Task 2 | from asynctnt_queue.task import Status 3 | from tests import BaseTarantoolTestCase 4 | 5 | 6 | class TaskTestCase(BaseTarantoolTestCase): 7 | def create_tube(self): 8 | q = Queue(self.conn) 9 | return q.tube("test_tube") 10 | 11 | def _data_obj(self): 12 | return { 13 | 'key': 'value' 14 | } 15 | 16 | async def test__task_tube(self): 17 | tube = self.create_tube() 18 | t = await tube.put(self._data_obj()) 19 | self.assertIs(t.tube, tube, 'tube is the same object') 20 | 21 | async def test__task_repr(self): 22 | tube = self.create_tube() 23 | t = await tube.put(self._data_obj()) 24 | self.assertEqual(repr(t), '') 25 | 26 | async def test__task_ack(self): 27 | tube = self.create_tube() 28 | t = await tube.put(self._data_obj()) 29 | t2 = await tube.take() 30 | t2 = await t2.ack() 31 | self.assertEqual(t2.task_id, t.task_id) 32 | self.assertEqual(t2.status, Status.EXECUTED) 33 | self.assertEqual(t2.data, t.data) 34 | 35 | async def test__task_release(self): 36 | tube = self.create_tube() 37 | t = await tube.put(self._data_obj()) 38 | t2 = await tube.take() 39 | t2 = await t2.release() 40 | self.assertEqual(t2.task_id, t.task_id) 41 | self.assertEqual(t2.status, Status.READY) 42 | self.assertEqual(t2.data, t.data) 43 | 44 | async def test__task_release_delay(self): 45 | tube = self.create_tube() 46 | t = await tube.put(self._data_obj()) 47 | t2 = await tube.take() 48 | t2 = await t2.release(delay=5) 49 | self.assertEqual(t2.task_id, t.task_id) 50 | self.assertEqual(t2.status, Status.DELAYED) 51 | self.assertEqual(t2.data, t.data) 52 | 53 | async def test__task_bury(self): 54 | tube = self.create_tube() 55 | t = await tube.put(self._data_obj()) 56 | t2 = await tube.take() 57 | t2 = await t2.bury() 58 | self.assertEqual(t2.task_id, t.task_id) 59 | self.assertEqual(t2.status, Status.BURIED) 60 | self.assertEqual(t2.data, t.data) 61 | 62 | async def test__task_peek(self): 63 | tube = self.create_tube() 64 | t = await tube.put(self._data_obj()) 65 | t2 = await tube.take() 66 | t2 = await t2.peek() 67 | self.assertEqual(t2.task_id, t.task_id) 68 | self.assertEqual(t2.status, Status.TAKEN) 69 | self.assertEqual(t2.data, t.data) 70 | 71 | async def test__task_touch(self): 72 | tube = self.create_tube() 73 | t = await tube.put(self._data_obj()) 74 | t2 = await tube.take() 75 | t2 = await t2.touch(1) 76 | self.assertEqual(t2.task_id, t.task_id) 77 | self.assertEqual(t2.status, Status.TAKEN) 78 | self.assertEqual(t2.data, t.data) 79 | 80 | async def test__task_delete(self): 81 | tube = self.create_tube() 82 | t = await tube.put(self._data_obj()) 83 | t2 = await tube.take() 84 | t2 = await t2.delete() 85 | self.assertEqual(t2.task_id, t.task_id) 86 | self.assertEqual(t2.status, Status.EXECUTED) 87 | self.assertEqual(t2.data, t.data) 88 | 89 | -------------------------------------------------------------------------------- /tests/test_tube.py: -------------------------------------------------------------------------------- 1 | from asynctnt_queue import Queue, Tube, Task 2 | from asynctnt_queue.task import Status 3 | from tests import BaseTarantoolTestCase 4 | 5 | 6 | class TubeTestCase(BaseTarantoolTestCase): 7 | def create_tube(self): 8 | q = Queue(self.conn) 9 | return q.tube("test_tube") 10 | 11 | def _data_obj(self): 12 | return { 13 | 'key': 'value' 14 | } 15 | 16 | async def test__tube_queue(self): 17 | q = Queue(self.conn) 18 | tube = q.tube('test_tube') 19 | self.assertIs(tube.queue, q, 'queue is the same object') 20 | 21 | async def test__tube_put(self): 22 | tube = self.create_tube() 23 | t = await tube.put(self._data_obj()) 24 | self.assertIsNotNone(t) 25 | self.assertIsInstance(t, Task) 26 | self.assertEqual(t.status, Status.READY) 27 | self.assertEqual(t.task_id, 0) # first task has id = 0 28 | self.assertEqual(t.data, self._data_obj()) 29 | 30 | async def test__tube_put_options(self): 31 | tube = self.create_tube() 32 | t = await tube.put(self._data_obj(), pri=4, ttl=10, ttr=1, delay=0.2) 33 | self.assertIsNotNone(t) 34 | self.assertIsInstance(t, Task) 35 | self.assertEqual(t.status, Status.DELAYED) 36 | self.assertEqual(t.task_id, 0) # first task has id = 0 37 | self.assertEqual(t.data, self._data_obj()) 38 | 39 | async def test__tube_take(self): 40 | tube = self.create_tube() 41 | t = await tube.put(self._data_obj()) 42 | taken_t = await tube.take() 43 | self.assertEqual(taken_t.task_id, t.task_id, 'task id equal') 44 | self.assertEqual(taken_t.status, Status.TAKEN) 45 | self.assertDictEqual(taken_t.data, t.data) 46 | 47 | async def test__tube_take_no_tasks(self): 48 | tube = self.create_tube() 49 | taken_t = await tube.take(0.5) 50 | self.assertIsNone(taken_t) 51 | 52 | async def test__tube_ack(self): 53 | tube = self.create_tube() 54 | t = await tube.put(self._data_obj()) 55 | t2 = await tube.take() 56 | t2 = await tube.ack(t2.task_id) 57 | self.assertEqual(t2.task_id, t.task_id) 58 | self.assertEqual(t2.status, Status.EXECUTED) 59 | self.assertEqual(t2.data, t.data) 60 | 61 | async def test__tube_release(self): 62 | tube = self.create_tube() 63 | t = await tube.put(self._data_obj()) 64 | t2 = await tube.take() 65 | t2 = await tube.release(t2.task_id) 66 | self.assertEqual(t2.task_id, t.task_id) 67 | self.assertEqual(t2.status, Status.READY) 68 | self.assertEqual(t2.data, t.data) 69 | 70 | async def test__tube_release_delay(self): 71 | tube = self.create_tube() 72 | t = await tube.put(self._data_obj()) 73 | t2 = await tube.take() 74 | t2 = await tube.release(t2.task_id, delay=5) 75 | self.assertEqual(t2.task_id, t.task_id) 76 | self.assertEqual(t2.status, Status.DELAYED) 77 | self.assertEqual(t2.data, t.data) 78 | 79 | async def test__tube_bury(self): 80 | tube = self.create_tube() 81 | t = await tube.put(self._data_obj()) 82 | t2 = await tube.take() 83 | t2 = await tube.bury(t2.task_id) 84 | self.assertEqual(t2.task_id, t.task_id) 85 | self.assertEqual(t2.status, Status.BURIED) 86 | self.assertEqual(t2.data, t.data) 87 | 88 | async def test__tube_peek(self): 89 | tube = self.create_tube() 90 | t = await tube.put(self._data_obj()) 91 | t2 = await tube.take() 92 | t2 = await tube.peek(t2.task_id) 93 | self.assertEqual(t2.task_id, t.task_id) 94 | self.assertEqual(t2.status, Status.TAKEN) 95 | self.assertEqual(t2.data, t.data) 96 | 97 | async def test__tube_touch(self): 98 | tube = self.create_tube() 99 | t = await tube.put(self._data_obj()) 100 | t2 = await tube.take() 101 | t2 = await tube.touch(t2.task_id, 1) 102 | self.assertEqual(t2.task_id, t.task_id) 103 | self.assertEqual(t2.status, Status.TAKEN) 104 | self.assertEqual(t2.data, t.data) 105 | 106 | async def test__tube_kick(self): 107 | tube = self.create_tube() 108 | t = await tube.put(self._data_obj()) 109 | t2 = await tube.take() 110 | t2 = await tube.bury(t2.task_id) 111 | t3 = await tube.take(0.5) 112 | self.assertIsNone(t3, 'no tasks left') 113 | 114 | count = await tube.kick(1) 115 | self.assertEqual(count, 1) 116 | t3 = await tube.take(0.5) 117 | self.assertEqual(t3.task_id, t2.task_id) 118 | self.assertEqual(t3.status, Status.TAKEN) 119 | self.assertEqual(t3.data, t2.data) 120 | 121 | async def test__tube_delete(self): 122 | tube = self.create_tube() 123 | t = await tube.put(self._data_obj()) 124 | t2 = await tube.take() 125 | t2 = await tube.delete(t2.task_id) 126 | self.assertEqual(t2.task_id, t.task_id) 127 | self.assertEqual(t2.status, Status.EXECUTED) 128 | self.assertEqual(t2.data, t.data) 129 | 130 | async def test__tube_statistics(self): 131 | tube = self.create_tube() 132 | res = await tube.statistics() 133 | self.assertIsNotNone(res) 134 | 135 | --------------------------------------------------------------------------------