├── .circleci ├── circle_requirements.txt └── config.yml ├── .github ├── release-drafter-config.yml └── workflows │ ├── check-pypi.yml │ ├── codeql-analysis.yml │ ├── publish-pypi.yml │ └── release-drafter.yml ├── .gitignore ├── LICENSE ├── README.md ├── example.py ├── gearsclient ├── __init__.py └── redisgears_builder.py ├── poetry.lock ├── pyproject.toml ├── test.py └── tox.ini /.circleci/circle_requirements.txt: -------------------------------------------------------------------------------- 1 | poetry>=1.2.0 2 | tox>=3.23.1 3 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Python CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-python/ for more details 4 | # 5 | version: 2.1 6 | commands: 7 | 8 | abort_for_docs: 9 | steps: 10 | - run: 11 | name: Avoid tests for docs 12 | command: | 13 | if [[ $CIRCLE_BRANCH == *docs ]]; then 14 | echo "Identifies as documents PR, no testing required" 15 | circleci step halt 16 | fi 17 | 18 | abort_for_noci: 19 | steps: 20 | - run: 21 | name: Ignore CI for specific branches 22 | command: | 23 | if [[ $CIRCLE_BRANCH == *noci ]]; then 24 | echo "Identifies as actively ignoring CI, no testing required." 25 | circleci step halt 26 | fi 27 | 28 | 29 | early_return_for_forked_pull_requests: 30 | description: >- 31 | If this build is from a fork, stop executing the current job and return success. 32 | This is useful to avoid steps that will fail due to missing credentials. 33 | steps: 34 | - run: 35 | name: Early return if this build is from a forked PR 36 | command: | 37 | if [[ -n "$CIRCLE_PR_NUMBER" ]]; then 38 | echo "Nothing to do for forked PRs, so marking this step successful" 39 | circleci step halt 40 | fi 41 | 42 | # build_and_test: 43 | # docker: 44 | # - image: 'ubuntu:bionic' 45 | # steps: 46 | # - run: 47 | # name: installations 48 | # command: apt-get -qq update; apt-get install -y ca-certificates wget build-essential git python-pip 49 | # - run: 50 | # name: Redis_5_upgrade 51 | # command: cd ..; git clone https://github.com/antirez/redis.git; cd ./redis; git fetch; git checkout 6.0.9; make; make install 52 | # - run: 53 | # name: download_RedisGears 54 | # command: cd ..; wget http://redismodules.s3.amazonaws.com/redisgears/snapshots/redisgears.linux-bionic-x64.master.zip; apt-get install -y unzip; unzip redisgears.linux-bionic-x64.master.zip 55 | # - run: 56 | # name: download_RedisGears_deps 57 | # command: cd ..; wget http://redismodules.s3.amazonaws.com/redisgears/snapshots/redisgears-python.linux-bionic-x64.master.tgz; mkdir -p /var/opt/redislabs/modules/rg/; cd /var/opt/redislabs/modules/rg/; tar -xvf /root/redisgears-python.linux-bionic-x64.master.tgz 58 | # - checkout 59 | # - run: 60 | # name: install_redisgears_py_to_RedisGears_virtual_env 61 | # command: /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 setup.py install 62 | # - run: 63 | # name: install RLTest 64 | # command: /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m pip install git+https://github.com/Grokzen/redis-py-cluster.git@master git+https://github.com/RedisLabsModules/RLTest.git 65 | # - run: 66 | # name: run_tests 67 | # command: /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m RLTest --module ../redisgears.so --module-args "Plugin /var/opt/redislabs/modules/rg/plugin/gears_python.so" 68 | 69 | #workflows: 70 | # version: 2 71 | # commit: 72 | # jobs: 73 | # - build_and_test 74 | # nightly: 75 | # triggers: 76 | # - schedule: 77 | # cron: "0 0 * * *" 78 | # filters: 79 | # branches: 80 | # only: 81 | # - master 82 | # jobs: 83 | # - build_and_test 84 | 85 | build_and_test: 86 | steps: 87 | - checkout 88 | 89 | - run: 90 | name: install tox dependencies 91 | command: | 92 | /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m pip install --upgrade pip virtualenv setuptools 93 | /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m pip install -r .circleci/circle_requirements.txt 94 | 95 | - run: 96 | name: build sdist and wheels 97 | command: | 98 | /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m poetry build 99 | 100 | # - run: 101 | # name: lint 102 | # command: | 103 | # /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m tox -e linters 104 | # 105 | - run: 106 | name: run tests 107 | command: 108 | /var/opt/redislabs/modules/rg/python3_1.2.5/bin/python3 -m tox -e tests 109 | 110 | jobs: 111 | build: 112 | docker: 113 | - image: redislabs/redisgears:1.2.5 114 | 115 | steps: 116 | - build_and_test 117 | - store_artifacts: 118 | path: test-reports 119 | destination: test-reports 120 | 121 | nightly: 122 | docker: 123 | - image: redislabs/redisgears:1.2.5 124 | steps: 125 | - build_and_test 126 | - dockerize 127 | 128 | on-any-branch: &on-any-branch 129 | filters: 130 | branches: 131 | only: 132 | - /.*/ 133 | tags: 134 | ignore: /.*/ 135 | 136 | on-master: &on-master 137 | filters: 138 | branches: 139 | only: 140 | - master 141 | 142 | workflows: 143 | version: 2 144 | commit: 145 | jobs: 146 | - build: 147 | <<: *on-any-branch 148 | 149 | nightly: 150 | triggers: 151 | - schedule: 152 | cron: "0 0 * * *" 153 | <<: *on-master 154 | jobs: 155 | - build 156 | -------------------------------------------------------------------------------- /.github/release-drafter-config.yml: -------------------------------------------------------------------------------- 1 | name-template: 'Version $NEXT_PATCH_VERSION' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | categories: 4 | - title: 'Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: 'Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: 'Maintenance' 14 | label: 'chore' 15 | change-template: '- $TITLE (#$NUMBER)' 16 | exclude-labels: 17 | - 'skip-changelog' 18 | template: | 19 | ## Changes 20 | 21 | $CHANGES 22 | -------------------------------------------------------------------------------- /.github/workflows/check-pypi.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Check if required secrets are set to publish to Pypi 3 | 4 | on: push 5 | 6 | jobs: 7 | checksecret: 8 | name: check if PYPI_TOKEN and TESTPYPI_TOKEN are set in github secrets 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check PYPI_TOKEN 12 | env: 13 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 14 | run: | 15 | if ${{ env.PYPI_TOKEN == '' }} ; then 16 | echo "PYPI_TOKEN secret is not set" 17 | exit 1 18 | fi 19 | - name: Check TESTPYPI_TOKEN 20 | env: 21 | TESTPYPI_TOKEN: ${{ secrets.TESTPYPI_TOKEN }} 22 | run: | 23 | if ${{ env.TESTPYPI_TOKEN == '' }} ; then 24 | echo "TESTPYPI_TOKEN secret is not set" 25 | exit 1 26 | fi 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '19 13 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Pypi 2 | on: 3 | release: 4 | types: [ published ] 5 | 6 | jobs: 7 | pytest: 8 | name: Publish to PyPi 9 | runs-on: ubuntu-latest 10 | env: 11 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 12 | steps: 13 | - uses: actions/checkout@master 14 | 15 | - name: get version from tag 16 | id: get_version 17 | run: | 18 | realversion="${GITHUB_REF/refs\/tags\//}" 19 | realversion="${realversion//v/}" 20 | echo "::set-output name=VERSION::$realversion" 21 | 22 | - name: Set the version for publishing 23 | uses: ciiiii/toml-editor@1.0.0 24 | with: 25 | file: "pyproject.toml" 26 | key: "tool.poetry.version" 27 | value: "${{ steps.get_version.outputs.VERSION }}" 28 | 29 | - name: Set up Python 3.7 30 | uses: actions/setup-python@v1 31 | with: 32 | python-version: 3.7 33 | 34 | - name: Install Poetry 35 | uses: dschep/install-poetry-action@v1.3 36 | 37 | - name: Cache Poetry virtualenv 38 | uses: actions/cache@v1 39 | id: cache 40 | with: 41 | path: ~/.virtualenvs 42 | key: poetry-${{ hashFiles('**/poetry.lock') }} 43 | restore-keys: | 44 | poetry-${{ hashFiles('**/poetry.lock') }} 45 | 46 | - name: Set Poetry config 47 | run: | 48 | poetry config virtualenvs.in-project false 49 | poetry config virtualenvs.path ~/.virtualenvs 50 | 51 | - name: Install Dependencies 52 | run: poetry install 53 | if: steps.cache.outputs.cache-hit != 'true' 54 | 55 | - name: Publish to PyPI 56 | if: github.event_name == 'release' 57 | run: | 58 | poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }} --build 59 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "master" 14 | - uses: release-drafter/release-drafter@v5 15 | with: 16 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 17 | config-name: release-drafter-config.yml 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | requirements.txt 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![license](https://img.shields.io/github/license/RedisGears/redisgears-py.svg)](https://github.com/RedisGears/redisgears-py) 2 | [![PyPI version](https://badge.fury.io/py/redisgears-py.svg)](https://badge.fury.io/py/redisgears-py) 3 | [![CircleCI](https://circleci.com/gh/RedisGears/redisgears-py/tree/master.svg?style=svg)](https://circleci.com/gh/RedisGears/redisgears-py/tree/master) 4 | [![GitHub issues](https://img.shields.io/github/release/RedisGears/redisgears-py.svg)](https://github.com/RedisGears/redisgears-py/releases/latest) 5 | [![Codecov](https://codecov.io/gh/RedisGears/redisgears-py/branch/master/graph/badge.svg)](https://codecov.io/gh/RedisGears/redisgears-py) 6 | [![Known Vulnerabilities](https://snyk.io/test/github/RedisGears/redisgears-py/badge.svg?targetFile=pyproject.toml)](https://snyk.io/test/github/RedisGears/redisgears-pytargetFile=pyproject.toml) 7 | 8 | # redisgears-py 9 | [![Forum](https://img.shields.io/badge/Forum-RedisGears-blue)](https://forum.redis.com/c/modules/redisgears) 10 | [![Discord](https://img.shields.io/discord/697882427875393627?style=flat-square)](https://discord.gg/6yaVTtp) 11 | 12 | [RedisGears](http://redisgears.io) python client (support python3 only!) 13 | 14 | ## Example: Using the Python Client: 15 | ```python 16 | from gearsclient import GearsRemoteBuilder as GearsBuilder 17 | from gearsclient import execute 18 | import redis 19 | 20 | conn = redis.Redis(host='localhost', port=6379) 21 | 22 | # count for each genre how many times it appears 23 | 24 | res = GearsBuilder('KeysOnlyReader', r=conn).\ 25 | map(lambda x:execute('hget', x, 'genres')).\ 26 | filter(lambda x:x != '\\N').\ 27 | flatmap(lambda x: x.split(',')).\ 28 | map(lambda x: x.strip()).\ 29 | countby().\ 30 | run() 31 | 32 | 33 | for r in res[0]: 34 | print('%-15s: %d' % (r['key'], r['value'])) 35 | ``` 36 | 37 | ## Installing 38 | ``` 39 | pip install git+https://github.com/RedisGears/redisgears-py.git 40 | ``` 41 | Notice that the library also need to be installed in RedisGears virtual env. 42 | 43 | ## Developing 44 | 45 | 1. Create a virtualenv to manage your python dependencies, and ensure it's active. 46 | ```virtualenv -v venv``` 47 | 2. Install [pypoetry](https://python-poetry.org/) to manage your dependencies. 48 | ```pip install poetry``` 49 | 3. Install dependencies. 50 | ```poetry install``` 51 | 52 | [tox](https://tox.readthedocs.io/en/latest/) runs all tests as its default target. Running *tox* by itself will run unit tests. Ensure you have a running redis, with the module loaded. 53 | 54 | 55 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from gearsclient import GearsRemoteBuilder as GRB 2 | from gearsclient import execute, atomic 3 | import redis 4 | 5 | conn = redis.Redis(host='localhost', port=6379) 6 | 7 | def func(x): 8 | with atomic(): 9 | execute('hset', 'h', 'foo', 'bar') 10 | execute('hset', 'h', 'foo1', 'bar1') 11 | return x 12 | 13 | res = GRB('ShardsIDReader',r=conn).\ 14 | map(func).\ 15 | run() 16 | 17 | print(res) 18 | -------------------------------------------------------------------------------- /gearsclient/__init__.py: -------------------------------------------------------------------------------- 1 | from .redisgears_builder import GearsRemoteBuilder, log, gearsConfigGet, execute, atomic, hashtag 2 | -------------------------------------------------------------------------------- /gearsclient/redisgears_builder.py: -------------------------------------------------------------------------------- 1 | import redis 2 | import cloudpickle 3 | 4 | class GearsRemoteLocalGroupByStep(): 5 | def __init__(self, extractor, reducer): 6 | self.extractor = extractor 7 | self.reducer = reducer 8 | 9 | def AddToGB(self, gb): 10 | gb.localgroupby(self.extractor, self.reducer) 11 | 12 | class GearsRemoteAccumulateStep(): 13 | def __init__(self, accumulator): 14 | self.accumulator = accumulator 15 | 16 | def AddToGB(self, gb): 17 | gb.accumulate(self.accumulator) 18 | 19 | class GearsRemoteRepartitionStep(): 20 | def __init__(self, extractor): 21 | self.extractor = extractor 22 | 23 | def AddToGB(self, gb): 24 | gb.repartition(self.extractor) 25 | 26 | class GearsRemoteMapStep(): 27 | def __init__(self, callback): 28 | self.callback = callback 29 | 30 | def AddToGB(self, gb): 31 | gb.map(self.callback) 32 | 33 | class GearsRemoteForeachStep(): 34 | def __init__(self, callback): 35 | self.callback = callback 36 | 37 | def AddToGB(self, gb): 38 | gb.foreach(self.callback) 39 | 40 | class GearsRemoteFlatMapStep(): 41 | def __init__(self, callback): 42 | self.callback = callback 43 | 44 | def AddToGB(self, gb): 45 | gb.flatmap(self.callback) 46 | 47 | class GearsRemoteFilterStep(): 48 | def __init__(self, callback): 49 | self.callback = callback 50 | 51 | def AddToGB(self, gb): 52 | gb.filter(self.callback) 53 | 54 | class GearsRemoteCountByStep(): 55 | def __init__(self, callback): 56 | self.callback = callback 57 | 58 | def AddToGB(self, gb): 59 | gb.countby(self.callback) 60 | 61 | class GearsRemoteAvgStep(): 62 | def __init__(self, callback): 63 | self.callback = callback 64 | 65 | def AddToGB(self, gb): 66 | gb.avg(self.callback) 67 | 68 | class GearsRemoteCountStep(): 69 | def __init__(self): 70 | pass 71 | 72 | def AddToGB(self, gb): 73 | gb.count() 74 | 75 | class GearsRemoteDistinctStep(): 76 | def __init__(self): 77 | pass 78 | 79 | def AddToGB(self, gb): 80 | gb.distinct() 81 | 82 | class GearsRemoteAggregateStep(): 83 | def __init__(self, zero, seqOp, combOp): 84 | self.zero = zero 85 | self.seqOp = seqOp 86 | self.combOp = combOp 87 | 88 | def AddToGB(self, gb): 89 | gb.aggregate(self.zero, self.seqOp, self.combOp) 90 | 91 | class GearsRemoteAggregateByStep(): 92 | def __init__(self, extractor, zero, seqOp, combOp): 93 | self.extractor = extractor 94 | self.zero = zero 95 | self.seqOp = seqOp 96 | self.combOp = combOp 97 | 98 | def AddToGB(self, gb): 99 | gb.aggregateby(self.extractor, self.zero, self.seqOp, self.combOp) 100 | 101 | class GearsRemoteSortStep(): 102 | def __init__(self, reverse): 103 | self.reverse = reverse 104 | 105 | def AddToGB(self, gb): 106 | gb.sort(self.reverse) 107 | 108 | class GearsRemoteLimitStep(): 109 | def __init__(self, count, offset): 110 | self.count = count 111 | self.offset = offset 112 | 113 | def AddToGB(self, gb): 114 | gb.limit(self.count, self.offset) 115 | 116 | class GearsRemoteRunStep(): 117 | def __init__(self, arg, convertToStr, collect, kargs): 118 | self.arg = arg 119 | self.convertToStr = convertToStr 120 | self.collect = collect 121 | self.kargs = kargs 122 | 123 | def AddToGB(self, gb): 124 | gb.run(self.arg, self.convertToStr, self.collect, **self.kargs) 125 | 126 | class GearsRemoteRegisterStep(): 127 | def __init__(self, prefix, convertToStr, collect, kargs): 128 | self.prefix = prefix 129 | self.convertToStr = convertToStr 130 | self.collect = collect 131 | self.kargs = kargs 132 | 133 | def AddToGB(self, gb): 134 | gb.register(self.prefix, self.convertToStr, self.collect, **self.kargs) 135 | 136 | 137 | class GearsPipe(): 138 | def __init__(self, reader='KeysReader', defaultArg='*'): 139 | self.reader = reader 140 | self.defaultArg = defaultArg 141 | self.steps = [] 142 | 143 | def localgroupby(self, extractor, reducer): 144 | self.steps.append(GearsRemoteLocalGroupByStep(extractor, reducer)) 145 | return self 146 | 147 | def accumulate(self, accumulator): 148 | self.steps.append(GearsRemoteAccumulateStep(accumulator)) 149 | return self 150 | 151 | def repartition(self, extractor): 152 | self.steps.append(GearsRemoteRepartitionStep(extractor)) 153 | return self 154 | 155 | def map(self, callback): 156 | self.steps.append(GearsRemoteMapStep(callback)) 157 | return self 158 | 159 | def foreach(self, callback): 160 | self.steps.append(GearsRemoteForeachStep(callback)) 161 | return self 162 | 163 | def flatmap(self, callback): 164 | self.steps.append(GearsRemoteFlatMapStep(callback)) 165 | return self 166 | 167 | def filter(self, callback): 168 | self.steps.append(GearsRemoteFilterStep(callback)) 169 | return self 170 | 171 | def countby(self, callback): 172 | self.steps.append(GearsRemoteCountByStep(callback)) 173 | return self 174 | 175 | def avg(self, callback): 176 | self.steps.append(GearsRemoteAvgStep(callback)) 177 | return self 178 | 179 | def count(self): 180 | self.steps.append(GearsRemoteCountStep()) 181 | return self 182 | 183 | def distinct(self): 184 | self.steps.append(GearsRemoteDistinctStep()) 185 | return self 186 | 187 | def aggregate(self, zero, seqOp, combOp): 188 | self.steps.append(GearsRemoteAggregateStep(zero, seqOp, combOp)) 189 | return self 190 | 191 | def aggregateby(self, extractor, zero, seqOp, combOp): 192 | self.steps.append(GearsRemoteAggregateByStep(extractor, zero, seqOp, combOp)) 193 | return self 194 | 195 | def sort(self, reverse): 196 | self.steps.append(GearsRemoteSortStep(reverse)) 197 | return self 198 | 199 | def limit(self, count, offset): 200 | self.steps.append(GearsRemoteLimitStep(count, offset)) 201 | return self 202 | 203 | def run(self, arg, convertToStr, collect, **kargs): 204 | self.steps.append(GearsRemoteRunStep(arg, convertToStr, collect, kargs)) 205 | 206 | def register(self, prefix, convertToStr, collect, **kargs): 207 | self.steps.append(GearsRemoteRegisterStep(prefix, convertToStr, collect, kargs)) 208 | 209 | def createAndRun(self, GB): 210 | gb = GB(self.reader) 211 | for s in self.steps: 212 | s.AddToGB(gb) 213 | 214 | 215 | 216 | class GearsRemoteBuilder(): 217 | def __init__(self, reader='KeysReader', defaultArg='*', r=None, requirements=[], addClientToRequirements=True): 218 | if r is None: 219 | r = redis.Redis() 220 | self.r = r 221 | self.pipe = GearsPipe(reader, defaultArg) 222 | self.requirements = requirements 223 | if addClientToRequirements: 224 | self.requirements += ['git+https://github.com/RedisGears/redisgears-py.git'] 225 | if len(self.requirements) > 0: 226 | self.requirements = ['REQUIREMENTS'] + self.requirements 227 | 228 | def localgroupby(self, extractor, reducer): 229 | self.pipe.localgroupby(extractor, reducer) 230 | return self 231 | 232 | def accumulate(self, accumulator): 233 | self.pipe.accumulate(accumulator) 234 | return self 235 | 236 | def repartition(self, extractor): 237 | self.pipe.repartition(extractor) 238 | return self 239 | 240 | def map(self, callback): 241 | self.pipe.map(callback) 242 | return self 243 | 244 | def foreach(self, callback): 245 | self.pipe.foreach(callback) 246 | return self 247 | 248 | def flatmap(self, callback): 249 | self.pipe.flatmap(callback) 250 | return self 251 | 252 | def filter(self, callback): 253 | self.pipe.filter(callback) 254 | return self 255 | 256 | def countby(self, callback=lambda x: x): 257 | self.pipe.countby(callback) 258 | return self 259 | 260 | def avg(self, callback=lambda x: float(x)): 261 | self.pipe.avg(callback) 262 | return self 263 | 264 | def count(self): 265 | self.pipe.count() 266 | return self 267 | 268 | def distinct(self): 269 | self.pipe.distinct() 270 | return self 271 | 272 | def aggregate(self, zero, seqOp, combOp): 273 | self.pipe.aggregate(zero, seqOp, combOp) 274 | return self 275 | 276 | def aggregateby(self, extractor, zero, seqOp, combOp): 277 | self.pipe.aggregateby(extractor, zero, seqOp, combOp) 278 | return self 279 | 280 | def sort(self, reverse=True): 281 | self.pipe.sort(reverse) 282 | return self 283 | 284 | def limit(self, count, offset=0): 285 | self.pipe.limit(count, offset) 286 | return self 287 | 288 | def run(self, arg=None, collect=True, **kargs): 289 | self.map(lambda x: cloudpickle.dumps(x)) 290 | self.pipe.run(arg, False, collect, **kargs) 291 | selfBytes = cloudpickle.dumps(self.pipe) 292 | serverCode = ''' 293 | import cloudpickle 294 | p = cloudpickle.loads(%s) 295 | p.createAndRun(GB) 296 | ''' % selfBytes 297 | results = self.r.execute_command('RG.PYEXECUTE', serverCode, *self.requirements) 298 | res, errs = results 299 | res = [cloudpickle.loads(record) for record in res] 300 | return res, errs 301 | 302 | def register(self, prefix='*', convertToStr=True, collect=True, **kargs): 303 | self.pipe.register(prefix, convertToStr, collect, **kargs) 304 | selfBytes = cloudpickle.dumps(self.pipe) 305 | serverCode = ''' 306 | import cloudpickle 307 | p = cloudpickle.loads(%s) 308 | p.createAndRun(GB) 309 | ''' % selfBytes 310 | res = self.r.execute_command('RG.PYEXECUTE', serverCode, *self.requirements) 311 | return res 312 | 313 | def log(msg, level='notice'): 314 | from redisgears import log as redisLog 315 | redisLog(msg, level=level) 316 | 317 | def gearsConfigGet(key, default=None): 318 | from redisgears import config_get as redisConfigGet 319 | val = redisConfigGet(key) 320 | return val if val is not None else default 321 | 322 | def execute(*args): 323 | from redisgears import executeCommand as redisExecute 324 | return redisExecute(*args) 325 | 326 | def hashtag(): 327 | from redisgears import getMyHashTag as redisHashtag 328 | return redisHashtag() 329 | 330 | class atomic: 331 | def __init__(self): 332 | from redisgears import atomicCtx as redisAtomic 333 | self.atomic = redisAtomic() 334 | 335 | def __enter__(self): 336 | self.atomic.__enter__() 337 | return self 338 | 339 | def __exit__(self, type, value, traceback): 340 | self.atomic.__exit__() 341 | 342 | 343 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.1" 4 | description = "Atomic file writes." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "22.1.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=3.5" 16 | 17 | [package.extras] 18 | tests_no_zope = ["cloudpickle", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] 19 | tests = ["cloudpickle", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] 20 | docs = ["sphinx-notfound-page", "zope.interface", "sphinx", "furo"] 21 | dev = ["cloudpickle", "pre-commit", "sphinx-notfound-page", "sphinx", "furo", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] 22 | 23 | [[package]] 24 | name = "bandit" 25 | version = "1.7.1" 26 | description = "Security oriented static analyser for python code." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.5" 30 | 31 | [package.dependencies] 32 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 33 | GitPython = ">=1.0.1" 34 | PyYAML = ">=5.3.1" 35 | stevedore = ">=1.20.0" 36 | 37 | [[package]] 38 | name = "certifi" 39 | version = "2022.6.15" 40 | description = "Python package for providing Mozilla's CA Bundle." 41 | category = "dev" 42 | optional = false 43 | python-versions = ">=3.6" 44 | 45 | [[package]] 46 | name = "charset-normalizer" 47 | version = "2.0.12" 48 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=3.5.0" 52 | 53 | [package.extras] 54 | unicode_backport = ["unicodedata2"] 55 | 56 | [[package]] 57 | name = "cloudpickle" 58 | version = "1.6.0" 59 | description = "Extended pickling support for Python objects" 60 | category = "main" 61 | optional = false 62 | python-versions = ">=3.5" 63 | 64 | [[package]] 65 | name = "codecov" 66 | version = "2.1.12" 67 | description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" 68 | category = "dev" 69 | optional = false 70 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 71 | 72 | [package.dependencies] 73 | coverage = "*" 74 | requests = ">=2.7.9" 75 | 76 | [[package]] 77 | name = "colorama" 78 | version = "0.4.5" 79 | description = "Cross-platform colored terminal text." 80 | category = "dev" 81 | optional = false 82 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 83 | 84 | [[package]] 85 | name = "coverage" 86 | version = "6.2" 87 | description = "Code coverage measurement for Python" 88 | category = "dev" 89 | optional = false 90 | python-versions = ">=3.6" 91 | 92 | [package.extras] 93 | 94 | [[package]] 95 | name = "distlib" 96 | version = "0.3.6" 97 | description = "Distribution utilities" 98 | category = "dev" 99 | optional = false 100 | python-versions = "*" 101 | 102 | [[package]] 103 | name = "distro" 104 | version = "1.7.0" 105 | description = "Distro - an OS platform information API" 106 | category = "dev" 107 | optional = false 108 | python-versions = ">=3.6" 109 | 110 | [[package]] 111 | name = "filelock" 112 | version = "3.4.1" 113 | description = "A platform independent file lock." 114 | category = "dev" 115 | optional = false 116 | python-versions = ">=3.6" 117 | 118 | [package.extras] 119 | testing = ["pytest-timeout (>=1.4.2)", "pytest-cov", "pytest (>=4)", "coverage (>=4)", "covdefaults (>=1.2.0)"] 120 | docs = ["sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4.1)", "furo (>=2021.8.17b43)"] 121 | 122 | [[package]] 123 | name = "flake8" 124 | version = "3.9.2" 125 | description = "the modular source code checker: pep8 pyflakes and co" 126 | category = "dev" 127 | optional = false 128 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 129 | 130 | [package.dependencies] 131 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 132 | mccabe = ">=0.6.0,<0.7.0" 133 | pycodestyle = ">=2.7.0,<2.8.0" 134 | pyflakes = ">=2.3.0,<2.4.0" 135 | 136 | [[package]] 137 | name = "gitdb" 138 | version = "4.0.9" 139 | description = "Git Object Database" 140 | category = "dev" 141 | optional = false 142 | python-versions = ">=3.6" 143 | 144 | [package.dependencies] 145 | smmap = ">=3.0.1,<6" 146 | 147 | [[package]] 148 | name = "gitpython" 149 | version = "3.1.20" 150 | description = "Python Git Library" 151 | category = "dev" 152 | optional = false 153 | python-versions = ">=3.6" 154 | 155 | [package.dependencies] 156 | gitdb = ">=4.0.1,<5" 157 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} 158 | 159 | [[package]] 160 | name = "idna" 161 | version = "3.3" 162 | description = "Internationalized Domain Names in Applications (IDNA)" 163 | category = "dev" 164 | optional = false 165 | python-versions = ">=3.5" 166 | 167 | [[package]] 168 | name = "importlib-metadata" 169 | version = "4.8.3" 170 | description = "Read metadata from Python packages" 171 | category = "dev" 172 | optional = false 173 | python-versions = ">=3.6" 174 | 175 | [package.dependencies] 176 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 177 | zipp = ">=0.5" 178 | 179 | [package.extras] 180 | testing = ["importlib-resources (>=1.3)", "pytest-mypy", "pytest-black (>=0.3.7)", "pytest-perf (>=0.9.2)", "flufl.flake8", "pyfakefs", "pep517", "packaging", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=6)"] 181 | perf = ["ipython"] 182 | docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] 183 | 184 | [[package]] 185 | name = "importlib-resources" 186 | version = "5.4.0" 187 | description = "Read resources from Python packages" 188 | category = "dev" 189 | optional = false 190 | python-versions = ">=3.6" 191 | 192 | [package.dependencies] 193 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 194 | 195 | [package.extras] 196 | testing = ["pytest-mypy", "pytest-black (>=0.3.7)", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=6)"] 197 | docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] 198 | 199 | [[package]] 200 | name = "iniconfig" 201 | version = "1.1.1" 202 | description = "iniconfig: brain-dead simple config-ini parsing" 203 | category = "dev" 204 | optional = false 205 | python-versions = "*" 206 | 207 | [[package]] 208 | name = "mccabe" 209 | version = "0.6.1" 210 | description = "McCabe checker, plugin for flake8" 211 | category = "dev" 212 | optional = false 213 | python-versions = "*" 214 | 215 | [[package]] 216 | name = "packaging" 217 | version = "21.3" 218 | description = "Core utilities for Python packages" 219 | category = "dev" 220 | optional = false 221 | python-versions = ">=3.6" 222 | 223 | [package.dependencies] 224 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 225 | 226 | [[package]] 227 | name = "pbr" 228 | version = "5.10.0" 229 | description = "Python Build Reasonableness" 230 | category = "dev" 231 | optional = false 232 | python-versions = ">=2.6" 233 | 234 | [[package]] 235 | name = "platformdirs" 236 | version = "2.4.0" 237 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 238 | category = "dev" 239 | optional = false 240 | python-versions = ">=3.6" 241 | 242 | [package.extras] 243 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 244 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 245 | 246 | [[package]] 247 | name = "pluggy" 248 | version = "1.0.0" 249 | description = "plugin and hook calling mechanisms for python" 250 | category = "dev" 251 | optional = false 252 | python-versions = ">=3.6" 253 | 254 | [package.dependencies] 255 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 256 | 257 | [package.extras] 258 | testing = ["pytest-benchmark", "pytest"] 259 | dev = ["tox", "pre-commit"] 260 | 261 | [[package]] 262 | name = "psutil" 263 | version = "5.8.0" 264 | description = "Cross-platform lib for process and system monitoring in Python." 265 | category = "dev" 266 | optional = false 267 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 268 | 269 | [package.extras] 270 | test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] 271 | 272 | [[package]] 273 | name = "py" 274 | version = "1.11.0" 275 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 276 | category = "dev" 277 | optional = false 278 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 279 | 280 | [[package]] 281 | name = "pycodestyle" 282 | version = "2.7.0" 283 | description = "Python style guide checker" 284 | category = "dev" 285 | optional = false 286 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 287 | 288 | [[package]] 289 | name = "pyflakes" 290 | version = "2.3.1" 291 | description = "passive checker of Python programs" 292 | category = "dev" 293 | optional = false 294 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 295 | 296 | [[package]] 297 | name = "pyparsing" 298 | version = "3.0.7" 299 | description = "Python parsing module" 300 | category = "dev" 301 | optional = false 302 | python-versions = ">=3.6" 303 | 304 | [package.extras] 305 | diagrams = ["railroad-diagrams", "jinja2"] 306 | 307 | [[package]] 308 | name = "pytest" 309 | version = "7.0.1" 310 | description = "pytest: simple powerful testing with Python" 311 | category = "dev" 312 | optional = false 313 | python-versions = ">=3.6" 314 | 315 | [package.dependencies] 316 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 317 | attrs = ">=19.2.0" 318 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 319 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 320 | iniconfig = "*" 321 | packaging = "*" 322 | pluggy = ">=0.12,<2.0" 323 | py = ">=1.8.2" 324 | 325 | [package.extras] 326 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 327 | 328 | [[package]] 329 | name = "pytest-cov" 330 | version = "2.5.0" 331 | description = "Pytest plugin for measuring coverage." 332 | category = "dev" 333 | optional = false 334 | python-versions = "*" 335 | 336 | [package.dependencies] 337 | coverage = ">=3.7.1" 338 | pytest = ">=2.6.0" 339 | 340 | [[package]] 341 | name = "pyyaml" 342 | version = "6.0" 343 | description = "YAML parser and emitter for Python" 344 | category = "dev" 345 | optional = false 346 | python-versions = ">=3.6" 347 | 348 | [[package]] 349 | name = "redis" 350 | version = "3.5.3" 351 | description = "Python client for Redis key-value store" 352 | category = "main" 353 | optional = false 354 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 355 | 356 | [package.extras] 357 | hiredis = ["hiredis (>=0.1.3)"] 358 | 359 | [[package]] 360 | name = "redis-py-cluster" 361 | version = "2.1.3" 362 | description = "Library for communicating with Redis Clusters. Built on top of redis-py lib" 363 | category = "dev" 364 | optional = false 365 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4" 366 | 367 | [package.dependencies] 368 | redis = ">=3.0.0,<4.0.0" 369 | 370 | [package.extras] 371 | hiredis = ["hiredis (>=0.1.3)"] 372 | 373 | [[package]] 374 | name = "requests" 375 | version = "2.27.1" 376 | description = "Python HTTP for Humans." 377 | category = "dev" 378 | optional = false 379 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 380 | 381 | [package.dependencies] 382 | certifi = ">=2017.4.17" 383 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 384 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 385 | urllib3 = ">=1.21.1,<1.27" 386 | 387 | [package.extras] 388 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 389 | socks = ["win-inet-pton", "PySocks (>=1.5.6,!=1.5.7)"] 390 | 391 | [[package]] 392 | name = "rltest" 393 | version = "0.4.2" 394 | description = "Redis Labs Test Framework, allow to run tests on redis and modules on a variety of environments" 395 | category = "dev" 396 | optional = false 397 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 398 | 399 | [package.dependencies] 400 | distro = ">=1.5.0,<2.0.0" 401 | psutil = "5.8.0" 402 | pytest-cov = "2.5" 403 | redis = ">=3.5.3,<4.0.0" 404 | redis-py-cluster = "*" 405 | 406 | [[package]] 407 | name = "six" 408 | version = "1.16.0" 409 | description = "Python 2 and 3 compatibility utilities" 410 | category = "main" 411 | optional = false 412 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 413 | 414 | [[package]] 415 | name = "smmap" 416 | version = "5.0.0" 417 | description = "A pure Python implementation of a sliding window memory map manager" 418 | category = "dev" 419 | optional = false 420 | python-versions = ">=3.6" 421 | 422 | [[package]] 423 | name = "stevedore" 424 | version = "3.5.0" 425 | description = "Manage dynamic plugins for Python applications" 426 | category = "dev" 427 | optional = false 428 | python-versions = ">=3.6" 429 | 430 | [package.dependencies] 431 | importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} 432 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 433 | 434 | [[package]] 435 | name = "toml" 436 | version = "0.10.2" 437 | description = "Python Library for Tom's Obvious, Minimal Language" 438 | category = "dev" 439 | optional = false 440 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 441 | 442 | [[package]] 443 | name = "tox" 444 | version = "3.26.0" 445 | description = "tox is a generic virtualenv management and test command line tool" 446 | category = "dev" 447 | optional = false 448 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 449 | 450 | [package.dependencies] 451 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 452 | filelock = ">=3.0.0" 453 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 454 | packaging = ">=14" 455 | pluggy = ">=0.12.0" 456 | py = ">=1.4.17" 457 | six = ">=1.14.0" 458 | toml = {version = ">=0.10.2", markers = "python_version <= \"3.6\""} 459 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 460 | 461 | [package.extras] 462 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 463 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] 464 | 465 | [[package]] 466 | name = "tox-poetry" 467 | version = "0.3.0" 468 | description = "Tox poetry plugin" 469 | category = "dev" 470 | optional = false 471 | python-versions = "*" 472 | 473 | [package.dependencies] 474 | pluggy = "*" 475 | toml = "*" 476 | tox = {version = ">=3.7.0", markers = "python_version >= \"3\""} 477 | 478 | [package.extras] 479 | test = ["pylint", "pycodestyle", "pytest", "coverage"] 480 | 481 | [[package]] 482 | name = "typing-extensions" 483 | version = "4.1.1" 484 | description = "Backported and Experimental Type Hints for Python 3.6+" 485 | category = "dev" 486 | optional = false 487 | python-versions = ">=3.6" 488 | 489 | [[package]] 490 | name = "urllib3" 491 | version = "1.26.12" 492 | description = "HTTP library with thread-safe connection pooling, file post, and more." 493 | category = "dev" 494 | optional = false 495 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" 496 | 497 | [package.extras] 498 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 499 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] 500 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 501 | 502 | [[package]] 503 | name = "virtualenv" 504 | version = "20.16.5" 505 | description = "Virtual Python Environment builder" 506 | category = "dev" 507 | optional = false 508 | python-versions = ">=3.6" 509 | 510 | [package.dependencies] 511 | distlib = ">=0.3.5,<1" 512 | filelock = ">=3.4.1,<4" 513 | importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} 514 | importlib-resources = {version = ">=5.4", markers = "python_version < \"3.7\""} 515 | platformdirs = ">=2.4,<3" 516 | 517 | [package.extras] 518 | docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] 519 | testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] 520 | 521 | [[package]] 522 | name = "vulture" 523 | version = "2.5" 524 | description = "Find dead code" 525 | category = "dev" 526 | optional = false 527 | python-versions = ">=3.6" 528 | 529 | [package.dependencies] 530 | toml = "*" 531 | 532 | [[package]] 533 | name = "zipp" 534 | version = "3.6.0" 535 | description = "Backport of pathlib-compatible object wrapper for zip files" 536 | category = "dev" 537 | optional = false 538 | python-versions = ">=3.6" 539 | 540 | [package.extras] 541 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 542 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 543 | 544 | [metadata] 545 | lock-version = "1.1" 546 | python-versions = "^3.6, <3.8" 547 | content-hash = "ffcd9de84cefdc3fa7cdf37c7168659dadfcbed7a5a6950dba2df9c45cc40c05" 548 | 549 | [metadata.files] 550 | atomicwrites = [ 551 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 552 | ] 553 | attrs = [ 554 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 555 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 556 | ] 557 | bandit = [ 558 | {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, 559 | {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, 560 | ] 561 | certifi = [ 562 | {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, 563 | {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, 564 | ] 565 | charset-normalizer = [ 566 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 567 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 568 | ] 569 | cloudpickle = [ 570 | {file = "cloudpickle-1.6.0-py3-none-any.whl", hash = "sha256:3a32d0eb0bc6f4d0c57fbc4f3e3780f7a81e6fee0fa935072884d58ae8e1cc7c"}, 571 | {file = "cloudpickle-1.6.0.tar.gz", hash = "sha256:9bc994f9e9447593bd0a45371f0e7ac7333710fcf64a4eb9834bf149f4ef2f32"}, 572 | ] 573 | codecov = [ 574 | {file = "codecov-2.1.12-py2.py3-none-any.whl", hash = "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47"}, 575 | {file = "codecov-2.1.12-py3.8.egg", hash = "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635"}, 576 | {file = "codecov-2.1.12.tar.gz", hash = "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1"}, 577 | ] 578 | colorama = [ 579 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 580 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 581 | ] 582 | coverage = [ 583 | {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, 584 | {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, 585 | {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, 586 | {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, 587 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, 588 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, 589 | {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, 590 | {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, 591 | {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, 592 | {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, 593 | {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, 594 | {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, 595 | {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, 596 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, 597 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, 598 | {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, 599 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, 600 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, 601 | {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, 602 | {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, 603 | {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, 604 | {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, 605 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, 606 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, 607 | {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, 608 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, 609 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, 610 | {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, 611 | {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, 612 | {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, 613 | {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, 614 | {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, 615 | {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, 616 | {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, 617 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, 618 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, 619 | {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, 620 | {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, 621 | {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, 622 | {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, 623 | {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, 624 | {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, 625 | {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, 626 | {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, 627 | {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, 628 | {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, 629 | {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, 630 | ] 631 | distlib = [ 632 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 633 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 634 | ] 635 | distro = [ 636 | {file = "distro-1.7.0-py3-none-any.whl", hash = "sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b"}, 637 | {file = "distro-1.7.0.tar.gz", hash = "sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39"}, 638 | ] 639 | filelock = [ 640 | {file = "filelock-3.4.1-py3-none-any.whl", hash = "sha256:a4bc51381e01502a30e9f06dd4fa19a1712eab852b6fb0f84fd7cce0793d8ca3"}, 641 | {file = "filelock-3.4.1.tar.gz", hash = "sha256:0f12f552b42b5bf60dba233710bf71337d35494fc8bdd4fd6d9f6d082ad45e06"}, 642 | ] 643 | flake8 = [ 644 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, 645 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, 646 | ] 647 | gitdb = [ 648 | {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, 649 | {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, 650 | ] 651 | gitpython = [ 652 | {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, 653 | {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, 654 | ] 655 | idna = [ 656 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 657 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 658 | ] 659 | importlib-metadata = [ 660 | {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, 661 | {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, 662 | ] 663 | importlib-resources = [ 664 | {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, 665 | {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, 666 | ] 667 | iniconfig = [ 668 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 669 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 670 | ] 671 | mccabe = [ 672 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 673 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 674 | ] 675 | packaging = [ 676 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 677 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 678 | ] 679 | pbr = [ 680 | {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"}, 681 | {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"}, 682 | ] 683 | platformdirs = [ 684 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, 685 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, 686 | ] 687 | pluggy = [ 688 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 689 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 690 | ] 691 | psutil = [ 692 | {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, 693 | {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, 694 | {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, 695 | {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, 696 | {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, 697 | {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, 698 | {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, 699 | {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, 700 | {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, 701 | {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, 702 | {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, 703 | {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, 704 | {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, 705 | {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, 706 | {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, 707 | {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, 708 | {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, 709 | {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, 710 | {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, 711 | {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, 712 | {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, 713 | {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, 714 | {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, 715 | {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, 716 | {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, 717 | {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, 718 | {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, 719 | {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, 720 | ] 721 | py = [ 722 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 723 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 724 | ] 725 | pycodestyle = [ 726 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, 727 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, 728 | ] 729 | pyflakes = [ 730 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, 731 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, 732 | ] 733 | pyparsing = [ 734 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 735 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 736 | ] 737 | pytest = [ 738 | {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, 739 | {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, 740 | ] 741 | pytest-cov = [ 742 | {file = "pytest-cov-2.5.0.tar.gz", hash = "sha256:f191bf605b0d044c18fcba89fd624135c701600546ac50e51a82fd725e51409d"}, 743 | {file = "pytest_cov-2.5.0-py2.py3-none-any.whl", hash = "sha256:267864bcab5fd16ec8655a0c74194ddaaa879a116d3e1c9d1e0cda0ec849a459"}, 744 | ] 745 | pyyaml = [ 746 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 747 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 748 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 749 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 750 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 751 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 752 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 753 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 754 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 755 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 756 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 757 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 758 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 759 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 760 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 761 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 762 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 763 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 764 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 765 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 766 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 767 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 768 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 769 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 770 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 771 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 772 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 773 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 774 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 775 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 776 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 777 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 778 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 779 | ] 780 | redis = [ 781 | {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, 782 | {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, 783 | ] 784 | redis-py-cluster = [ 785 | {file = "redis-py-cluster-2.1.3.tar.gz", hash = "sha256:686042b76bf58b7ba8c938826d0840186bec274de85a957e9170db38e3387223"}, 786 | {file = "redis_py_cluster-2.1.3-py2.py3-none-any.whl", hash = "sha256:38f08850fde469ffd76bced7309721114acc487e52b76f374a0502c34c69b4ec"}, 787 | ] 788 | requests = [ 789 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 790 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 791 | ] 792 | rltest = [ 793 | {file = "RLTest-0.4.2-py2.py3-none-any.whl", hash = "sha256:63c93828c9072e1e5751602e5bcb48a7a8ffd7509ddafff120aa73a2e6ff8146"}, 794 | {file = "RLTest-0.4.2.tar.gz", hash = "sha256:dc7a696ef67411230ee9eef94be8b2820d8b4801d8586feae3b4a2fbead22807"}, 795 | ] 796 | six = [ 797 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 798 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 799 | ] 800 | smmap = [ 801 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 802 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 803 | ] 804 | stevedore = [ 805 | {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, 806 | {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, 807 | ] 808 | toml = [ 809 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 810 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 811 | ] 812 | tox = [ 813 | {file = "tox-3.26.0-py2.py3-none-any.whl", hash = "sha256:bf037662d7c740d15c9924ba23bb3e587df20598697bb985ac2b49bdc2d847f6"}, 814 | {file = "tox-3.26.0.tar.gz", hash = "sha256:44f3c347c68c2c68799d7d44f1808f9d396fc8a1a500cbc624253375c7ae107e"}, 815 | ] 816 | tox-poetry = [ 817 | {file = "tox-poetry-0.3.0.tar.gz", hash = "sha256:605f43c80f86cc9a5946370b0bc1a7b4049d2c29ed67baa71f979e7f6111c5b1"}, 818 | {file = "tox_poetry-0.3.0-py2.py3-none-any.whl", hash = "sha256:d61dfe2e6b732dc9afceb0c2853d037850f96e28a875b39831f3e9337ece3740"}, 819 | ] 820 | typing-extensions = [ 821 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 822 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 823 | ] 824 | urllib3 = [ 825 | {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, 826 | {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, 827 | ] 828 | virtualenv = [ 829 | {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, 830 | {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, 831 | ] 832 | vulture = [ 833 | {file = "vulture-2.5-py2.py3-none-any.whl", hash = "sha256:a7c7e7a23b11e78840fdd821509d05a6134aa9fd60418fe39d60b3026fe698d9"}, 834 | {file = "vulture-2.5.tar.gz", hash = "sha256:2831694055eb2e36a09c3b7680934837102b9b6c0969206e3902d513612177c3"}, 835 | ] 836 | zipp = [ 837 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, 838 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, 839 | ] 840 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "gearsclient" 3 | version = "1.0.2" 4 | description = "RedisGears Python Client" 5 | authors = ["Redis "] 6 | license = "BSD-3-Clause" 7 | readme = "README.md" 8 | 9 | classifiers = [ 10 | 'Topic :: Database', 11 | 'Programming Language :: Python', 12 | 'Intended Audience :: Developers', 13 | 'Programming Language :: Python :: 3.6', 14 | 'Programming Language :: Python :: 3.7', 15 | 'License :: OSI Approved :: BSD License', 16 | 'Operating System :: OS Independent', 17 | 'Development Status :: 5 - Production/Stable' 18 | ] 19 | keywords = ["Redis JSON Extension"] 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.6, <3.8" 23 | redis = "3.5.3" 24 | six = "^1.16.0" 25 | cloudpickle = "^1.6.0" 26 | 27 | [tool.poetry.urls] 28 | url = "https://redisgears.io" 29 | repository = "https://github.com/RedisGears/redisgears-py" 30 | 31 | [tool.poetry.dev-dependencies] 32 | codecov = "^2.1.11" 33 | flake8 = "^3.9.2" 34 | tox = "^3.23.1" 35 | tox-poetry = "^0.3.0" 36 | bandit = "^1.7.0" 37 | vulture = "^2.3" 38 | distro = "^1.5.0" 39 | rltest = "0.4.2" 40 | 41 | [build-system] 42 | requires = ["poetry-core>=1.0.0"] 43 | build-backend = "poetry.core.masonry.api" 44 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from gearsclient import GearsRemoteBuilder as GRB 2 | from gearsclient import log, hashtag, execute, atomic 3 | 4 | counter = 0 5 | 6 | def getGB(env, reader='KeysReader'): 7 | return GRB(reader, r=env.getConnection(), addClientToRequirements=False) 8 | 9 | def test_map(env): 10 | env.cmd('set', 'x', '1') 11 | env.cmd('set', 'y', '2') 12 | env.cmd('set', 'z', '3') 13 | res = getGB(env).map(lambda x: x['value']).sort().run() 14 | env.assertEqual(res, (['1', '2', '3'], [])) 15 | 16 | def test_filter(env): 17 | env.cmd('set', 'x', '1') 18 | env.cmd('set', 'y', '2') 19 | env.cmd('set', 'z', '3') 20 | res = getGB(env).map(lambda x: x['value']).filter(lambda x: x=='1').run() 21 | env.assertEqual(res, (['1'], [])) 22 | 23 | def test_foreach(env): 24 | env.cmd('set', 'x', '1') 25 | env.cmd('set', 'y', '2') 26 | env.cmd('set', 'z', '3') 27 | def increase(x): 28 | global counter 29 | counter += 1 30 | 31 | # important to notice, the counte will increased on the server size and not on client side!! 32 | res = getGB(env).foreach(increase).map(lambda x: counter).run() 33 | env.assertEqual(res, ([1, 2, 3], [])) 34 | 35 | def test_flatmap(env): 36 | env.cmd('lpush', 'l', '1', '2', '3') 37 | res = getGB(env, 'KeysOnlyReader').map(lambda x: execute('lrange', x, '0', '-1')).flatmap(lambda x: x).run() 38 | env.assertEqual(res, (['1', '2', '3'], [])) 39 | 40 | def test_countby(env): 41 | env.cmd('set', 'x', '1') 42 | env.cmd('set', 'y', '1') 43 | env.cmd('set', 'z', '2') 44 | env.cmd('set', 't', '2') 45 | res = getGB(env).map(lambda x: x['value']).countby().map(lambda x: (x['key'], x['value'])).sort().run() 46 | env.assertEqual(res, ([('1', 2), ('2', 2)], [])) 47 | 48 | def test_avg(env): 49 | env.cmd('set', 'x', '1') 50 | env.cmd('set', 'y', '1') 51 | env.cmd('set', 'z', '2') 52 | env.cmd('set', 't', '2') 53 | res = getGB(env).map(lambda x: x['value']).avg().run() 54 | env.assertEqual(res, ([1.5], [])) 55 | 56 | def test_count(env): 57 | env.cmd('set', 'x', '1') 58 | env.cmd('set', 'y', '1') 59 | env.cmd('set', 'z', '2') 60 | env.cmd('set', 't', '2') 61 | res = getGB(env).count().run() 62 | env.assertEqual(res, ([4], [])) 63 | 64 | def test_distinct(env): 65 | env.cmd('set', 'x', '1') 66 | env.cmd('set', 'y', '1') 67 | env.cmd('set', 'z', '2') 68 | env.cmd('set', 't', '2') 69 | res = getGB(env).map(lambda x: x['value']).distinct().count().run() 70 | env.assertEqual(res, ([2], [])) 71 | 72 | def test_aggregate(env): 73 | env.cmd('set', 'x', '1') 74 | env.cmd('set', 'y', '1') 75 | env.cmd('set', 'z', '2') 76 | env.cmd('set', 't', '2') 77 | res = getGB(env).map(lambda x: x['value']).aggregate(0, lambda a, r: a + int(r), lambda a, r: a + r).run() 78 | env.assertEqual(res, ([6], [])) 79 | 80 | def test_aggregateby(env): 81 | env.cmd('set', 'x', '1') 82 | env.cmd('set', 'y', '1') 83 | env.cmd('set', 'z', '2') 84 | env.cmd('set', 't', '2') 85 | res = getGB(env).map(lambda x: x['value']).aggregateby(lambda x: x, 0, lambda k, a, r: a + int(r), lambda k, a, r: a + r).map(lambda x: (x['key'], x['value'])).sort().run() 86 | env.assertEqual(res, ([('1', 2), ('2', 4)], [])) 87 | 88 | def test_limit(env): 89 | env.cmd('set', 'x', '1') 90 | env.cmd('set', 'y', '1') 91 | env.cmd('set', 'z', '2') 92 | env.cmd('set', 't', '2') 93 | res = getGB(env).map(lambda x: x['value']).sort().limit(1).run() 94 | env.assertEqual(res, (['1'], [])) 95 | 96 | def test_sort(env): 97 | env.cmd('set', 'x', '1') 98 | env.cmd('set', 'y', '1') 99 | env.cmd('set', 'z', '2') 100 | env.cmd('set', 't', '2') 101 | res = getGB(env).map(lambda x: x['key']).sort().run() 102 | env.assertEqual(res, (['t', 'x', 'y', 'z'], [])) 103 | 104 | def test_hashtag(env): 105 | res = getGB(env, 'ShardsIDReader').map(lambda x: hashtag()).run() 106 | env.assertEqual(res, (['06S'], [])) 107 | 108 | def test_register(env): 109 | res = getGB(env, 'CommandReader').register(trigger='test') 110 | env.assertEqual(res, b'OK') 111 | env.expect('RG.TRIGGER', 'test', 'this', 'is', 'a', 'test').equal([b"['test', 'this', 'is', 'a', 'test']"]) 112 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = true 3 | envlist = tests,linters 4 | 5 | [flake8] 6 | max-complexity = 10 7 | ignore = E501,C901 8 | exclude =.git,.tox,dist,doc,*/__pycache__/* 9 | 10 | # These are the tests that run in CI. They can be run locally in a redislabs/redisgears:edge docker 11 | [testenv:tests] 12 | commands_pre = 13 | pip install --upgrade pip 14 | {envdir}/bin/python -m pip install poetry 15 | {envdir}/bin/poetry config virtualenvs.create false 16 | {envdir}/bin/python -m poetry export --without-hashes --format=requirements.txt -o requirements.txt --dev 17 | {envdir}/bin/python -m pip install -r requirements.txt 18 | {envdir}/bin/python -m pip install gearsclient 19 | commands = {envdir}/bin/python -m RLTest --module /var/opt/redislabs/lib/modules/redisgears.so --module-args "Plugin /var/opt/redislabs/modules/rg/plugin/gears_python.so" 20 | 21 | [testenv:linters] 22 | commands_pre = 23 | pip install --upgrade pip 24 | {envdir}/bin/python -m pip install poetry 25 | {envdir}/bin/python -m poetry export --without-hashes --format=requirements.txt -o requirements.txt --dev 26 | {envdir}/bin/python -m pip install -r requirements.txt 27 | commands = 28 | # flake8 --show-source 29 | {envdir}/bin/vulture gearsclient --min-confidence 80 30 | {envdir}/bin/bandit gearsclient/** 31 | --------------------------------------------------------------------------------