pax_global_header00006660000000000000000000000064150456740520014522gustar00rootroot0000000000000052 comment=76a5e8662a057864e61276e8b026298cc41518d0 valkey-io-valkey-py-350fd2d/000077500000000000000000000000001504567405200157525ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/.coveragerc000066400000000000000000000000261504567405200200710ustar00rootroot00000000000000[run] source = valkey valkey-io-valkey-py-350fd2d/.dockerignore000066400000000000000000000000561504567405200204270ustar00rootroot00000000000000**/__pycache__ **/*.pyc .coverage .coverage.* valkey-io-valkey-py-350fd2d/.flake8000066400000000000000000000004501504567405200171240ustar00rootroot00000000000000[flake8] max-line-length = 88 exclude = *.egg-info, *.pyc, .git, .venv*, build, docs/*, dist, docker, venv*, .venv*, whitelist.py, tasks.py ignore = E126 E203 E701 E704 F405 N801 N802 N803 N806 N815 W503 valkey-io-valkey-py-350fd2d/.github/000077500000000000000000000000001504567405200173125ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/.github/CODEOWNERS000066400000000000000000000000501504567405200207000ustar00rootroot00000000000000* @mkmkme @ahmedsobeh @smeso @bogdanp05 valkey-io-valkey-py-350fd2d/.github/ISSUE_TEMPLATE/000077500000000000000000000000001504567405200214755ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/.github/ISSUE_TEMPLATE/1-bug.md000066400000000000000000000010611504567405200227300ustar00rootroot00000000000000 **Version**: What valkey-py and what valkey version is the issue happening on? **Platform**: What platform / version? (For example Python 3.5.1 on Windows 7 / Ubuntu 15.10 / Azure) **Description**: Description of your issue, stack traces from errors and code that reproduces the issue valkey-io-valkey-py-350fd2d/.github/ISSUE_TEMPLATE/2-feature.md000066400000000000000000000002531504567405200236110ustar00rootroot00000000000000 **Description**: Description of your feature request **Example use cases**: List some possible use cases for this feature valkey-io-valkey-py-350fd2d/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002451504567405200234660ustar00rootroot00000000000000blank_issues_enabled: true contact_links: - name: Security Issues url: mailto:security@lists.valkey.io about: Please report security vulnerabilities here. valkey-io-valkey-py-350fd2d/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000012671504567405200231210ustar00rootroot00000000000000### Pull Request check-list - [ ] Do tests and lints pass with this change? - [ ] Do the CI tests pass with this change (enable it first in your forked repo and wait for the github action build to finish)? - [ ] Is the new or changed code fully tested? - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? - [ ] Is there an example added to the examples folder (if applicable)? ### Description of change valkey-io-valkey-py-350fd2d/.github/dependabot.yml000066400000000000000000000002311504567405200221360ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" labels: - "maintenance" schedule: interval: "monthly" valkey-io-valkey-py-350fd2d/.github/release-drafter-config.yml000066400000000000000000000020551504567405200243470ustar00rootroot00000000000000name-template: '$NEXT_MINOR_VERSION' tag-template: 'v$NEXT_MINOR_VERSION' filter-by-commitish: true commitish: main autolabeler: - label: 'maintenance' files: - '*.md' - '.github/*' - label: 'bug' branch: - '/bug-.+' - label: 'maintenance' branch: - '/maintenance-.+' - label: 'feature' branch: - '/feature-.+' categories: - title: '๐Ÿ”ฅ Breaking Changes' labels: - 'breakingchange' - title: '๐Ÿงช Experimental Features' labels: - 'experimental' - title: '๐Ÿš€ New Features' labels: - 'feature' - 'enhancement' - title: '๐Ÿ› Bug Fixes' labels: - 'fix' - 'bugfix' - 'bug' - 'BUG' - title: '๐Ÿงฐ Maintenance' labels: - 'maintenance' - 'dependencies' - 'documentation' - 'docs' - 'testing' change-template: '- $TITLE (#$NUMBER)' exclude-labels: - 'skip-changelog' template: | # Changes $CHANGES ## Contributors We'd like to thank all the contributors who worked on this release! $CONTRIBUTORS valkey-io-valkey-py-350fd2d/.github/spellcheck-settings.yml000066400000000000000000000010251504567405200240060ustar00rootroot00000000000000matrix: - name: Markdown expect_match: false aspell: lang: en d: en_US ignore-case: true dictionary: wordlists: - .github/wordlist.txt output: wordlist.dic pipeline: - pyspelling.filters.markdown: markdown_extensions: - markdown.extensions.extra: - pyspelling.filters.html: comments: false attributes: - alt ignores: - ':matches(code, pre)' - code - pre - blockquote - img sources: - '*.md' - 'docs/*.rst' - 'docs/*.ipynb' valkey-io-valkey-py-350fd2d/.github/wordlist.txt000066400000000000000000000027251504567405200217300ustar00rootroot00000000000000APM ARGV BFCommands CFCommands CMSCommands ClusterNode ClusterNodes ClusterPipeline ClusterPubSub ConnectionPool CoreCommands DCO DeveloperCertificate EVAL EVALSHA GraphCommands Grokzen's INCR IOError Instrumentations JSONCommands Jaeger Ludovico Magnocavallo McCurdy Mesoraca NOSCRIPT NUMPAT NUMPT NUMSUB OSS OpenCensus OpenTelemetry OpenTracing Otel PubSub READONLY RediSearch RedisBloom RedisGraph RedisInstrumentor RedisJSON RedisTimeSeries SHA SearchCommands SentinelCommands SentinelConnectionPool Sharded Solovyov SpanKind Specfiying StatusCode TCP TOPKCommands TimeSeriesCommands Uptrace Valkey's ValkeyCluster ValkeyClusterCommands ValkeyClusterException ValkeyClusters ValkeyInstrumentor ValueError WATCHed WatchError aiven api args async asyncio autoclass automodule backoff bdb behaviour bool boolean booleans bysource charset configs del dev docstring docstrings eg exc faq firsttimersonly fo genindex gmail html http https idx iff ini io json keyslot keyspace kwarg libvalkey linters localhost lua makeapullrequest maxdepth mesoraca mget microservice microservices mset multikey mykey nonatomic observability opentelemetry oss performant placeholderkv pmessage png pre psubscribe pubsub punsubscribe py pypi quickstart readonly readwrite redis redismodules reinitialization replicaof repo runtime salvatore sedrik sexualized sharded socio ssl str stunnel subcommands thevalueofmykey timeseries toctree topk triaging txt un unicode url valkey valkeymodules virtualenv www md yaml valkey-io-valkey-py-350fd2d/.github/workflows/000077500000000000000000000000001504567405200213475ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/.github/workflows/codeql-analysis.yml000066400000000000000000000043701504567405200251660ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š https://git.io/JvXDl # โœ๏ธ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 valkey-io-valkey-py-350fd2d/.github/workflows/dco.yml000066400000000000000000000002021504567405200226310ustar00rootroot00000000000000name: DCO Check on: [pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: tisonkun/actions-dco@v1.1 valkey-io-valkey-py-350fd2d/.github/workflows/docs.yaml000066400000000000000000000020471504567405200231660ustar00rootroot00000000000000name: Docs CI on: push: branches: - main - '[0-9].[0-9]' pull_request: branches: - main - '[0-9].[0-9]' schedule: - cron: '0 1 * * *' # nightly build concurrency: group: ${{ github.event.pull_request.number || github.ref }}-docs cancel-in-progress: true permissions: contents: read # to fetch code (actions/checkout) jobs: build-docs: name: Build docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.9 cache: 'pip' - name: install deps run: | sudo apt-get update -yqq sudo apt-get install -yqq pandoc make - name: run code linters run: | pip install -r requirements.txt -r dev_requirements.txt -r docs/requirements.txt invoke build-docs - name: upload docs uses: actions/upload-artifact@v4 with: name: valkey-py-docs path: | docs/_build/html valkey-io-valkey-py-350fd2d/.github/workflows/install_and_test.sh000077500000000000000000000016631504567405200252430ustar00rootroot00000000000000#!/bin/bash set -e SUFFIX=$1 if [ -z ${SUFFIX} ]; then echo "Supply valid python package extension such as whl or tar.gz. Exiting." exit 3 fi script=`pwd`/${BASH_SOURCE[0]} HERE=`dirname ${script}` ROOT=`realpath ${HERE}/../..` cd ${ROOT} DESTENV=${ROOT}/.venvforinstall if [ -d ${DESTENV} ]; then rm -rf ${DESTENV} fi python -m venv ${DESTENV} source ${DESTENV}/bin/activate pip install --upgrade --quiet pip pip install --quiet -r dev_requirements.txt invoke devenv invoke package # find packages PKG=`ls ${ROOT}/dist/*.${SUFFIX}` ls -l ${PKG} TESTDIR=${ROOT}/STAGETESTS if [ -d ${TESTDIR} ]; then rm -rf ${TESTDIR} fi mkdir ${TESTDIR} cp -R ${ROOT}/tests ${TESTDIR}/tests cd ${TESTDIR} # install, run tests pip install ${PKG} # Valkey tests pytest -m 'not onlycluster' # ValkeyCluster tests CLUSTER_URL="valkey://localhost:16379/0" pytest -m 'not onlynoncluster and not valkeymod and not ssl' --valkey-url=${CLUSTER_URL} valkey-io-valkey-py-350fd2d/.github/workflows/integration.yaml000066400000000000000000000135621504567405200245650ustar00rootroot00000000000000name: CI on: push: paths-ignore: - 'docs/**' - '**/*.rst' - '**/*.md' branches: - main - '[0-9].[0-9]' pull_request: branches: - main - '[0-9].[0-9]' schedule: - cron: '0 1 * * *' # nightly build concurrency: group: ${{ github.event.pull_request.number || github.ref }}-integration cancel-in-progress: true permissions: contents: read # to fetch code (actions/checkout) jobs: dependency-audit: name: Dependency audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pypa/gh-action-pip-audit@v1.1.0 with: inputs: requirements.txt dev_requirements.txt ignore-vulns: | GHSA-w596-4wvx-j9j6 # subversion related git pull, dependency for pytest. There is no impact here. lint: name: Code linters runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.9 cache: 'pip' - name: run code linters run: | pip install -r dev_requirements.txt invoke linters get-date: name: Get current date for cache outputs: current_date: ${{ steps.date.outputs.current_date }} runs-on: ubuntu-latest steps: - name: Set current date id: date run: echo "current_date=$(date -u +'%Y%m%d')" >> $GITHUB_OUTPUT populate-cache: runs-on: ubuntu-latest needs: [get-date, dependency-audit, lint] timeout-minutes: 60 name: Update docker cache steps: - uses: actions/checkout@v4 - name: Cache docker images id: custom-cache uses: actions/cache@v4 with: path: ./custom-cache/ key: custom-cache-${{ needs.get-date.outputs.current_date }} - if: ${{ steps.custom-cache.outputs.cache-hit != 'true' }} name: Update Cache run: | mkdir -p ./custom-cache/ docker compose --profile all build docker pull valkey/valkey:latest docker save valkey-py-stunnel:latest valkey-py-cluster:latest valkey/valkey:latest -o ./custom-cache/all.tar run-tests: runs-on: ubuntu-latest needs: [populate-cache, get-date] timeout-minutes: 60 strategy: max-parallel: 15 fail-fast: false matrix: python-version: ['3.9', '3.10', '3.11', '3.11.1', '3.12', '3.13', 'pypy-3.9', 'pypy-3.10'] test-type: ['standalone', 'cluster'] connection-type: ['libvalkey', 'plain'] protocol-version: ['2','3'] env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true name: Python ${{ matrix.python-version }} RESP${{ matrix.protocol-version }} ${{matrix.test-type}}-${{matrix.connection-type}} tests steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Cache docker images id: custom-cache uses: actions/cache@v4 with: path: ./custom-cache/ fail-on-cache-miss: true key: custom-cache-${{ needs.get-date.outputs.current_date }} - name: Use Cache run: docker image load -i ./custom-cache/all.tar - name: run tests run: | pip install -U setuptools wheel pip install -r requirements.txt pip install -r dev_requirements.txt if [ "${{matrix.connection-type}}" == "libvalkey" ]; then pip install "libvalkey>=4.0.1" fi invoke devenv if [[ "${{matrix.test-type}}" == "standalone" ]]; then ./util/wait-for-it.sh localhost:6379 else ./util/wait-for-it.sh localhost:16379 fi invoke ${{matrix.test-type}}-tests --protocol=${{ matrix.protocol-version }} if [[ "${{matrix.python-version}}" != pypy-* ]]; then invoke ${{matrix.test-type}}-tests --uvloop --protocol=${{ matrix.protocol-version }} fi - uses: actions/upload-artifact@v4 if: success() || failure() with: name: pytest-results-${{matrix.test-type}}-${{matrix.connection-type}}-${{matrix.python-version}}-RESP${{ matrix.protocol-version }} path: '${{matrix.test-type}}*results.xml' - name: Upload codecov coverage uses: codecov/codecov-action@v5 with: fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} build_and_test_package: name: Validate building and installing the package runs-on: ubuntu-latest needs: [run-tests, get-date, populate-cache] strategy: fail-fast: false matrix: extension: ['tar.gz', 'whl'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.9 - name: Cache docker images id: custom-cache uses: actions/cache@v4 with: path: ./custom-cache/ fail-on-cache-miss: true key: custom-cache-${{ needs.get-date.outputs.current_date }} - name: Use Cache run: docker image load -i ./custom-cache/all.tar - name: Run installed unit tests run: | bash .github/workflows/install_and_test.sh ${{ matrix.extension }} install_package_from_commit: name: Install package from commit hash runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ['3.9', '3.10', '3.11', '3.11.1', '3.12', '3.13', 'pypy-3.9', 'pypy-3.10'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: install from pip run: | pip install --quiet git+${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git@${GITHUB_SHA} valkey-io-valkey-py-350fd2d/.github/workflows/pypi-publish.yaml000066400000000000000000000014771504567405200246710ustar00rootroot00000000000000name: Publish tag to Pypi on: release: types: [published] permissions: contents: read # to fetch code (actions/checkout) jobs: build_and_package: runs-on: ubuntu-latest environment: pypi permissions: id-token: write steps: - uses: actions/checkout@v4 - name: install python uses: actions/setup-python@v5 with: python-version: 3.9 - name: Install dev tools run: | pip install -r dev_requirements.txt pip install twine wheel - name: Build package run: | python setup.py build python setup.py sdist bdist_wheel - name: Basic package test prior to upload run: | twine check dist/* - name: Publish to Pypi uses: pypa/gh-action-pypi-publish@release/v1 valkey-io-valkey-py-350fd2d/.github/workflows/rebase-merge.yml000066400000000000000000000003411504567405200244260ustar00rootroot00000000000000name: Enforce semi-linear git history on: pull_request jobs: forbid-merge-commits: runs-on: ubuntu-latest steps: - name: Run Forbid Merge Commits Action uses: motlin/forbid-merge-commits-action@main valkey-io-valkey-py-350fd2d/.github/workflows/release-drafter.yml000066400000000000000000000014341504567405200251410ustar00rootroot00000000000000name: Release Drafter on: push: # branches to consider in the event; optional, defaults to all branches: - main permissions: {} jobs: update_release_draft: permissions: pull-requests: write # to add label to PR (release-drafter/release-drafter) contents: write # to create a github release (release-drafter/release-drafter) runs-on: ubuntu-latest environment: release-drafter steps: # Drafts your next Release notes as Pull Requests are merged into "main" - uses: release-drafter/release-drafter@v6 with: # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml config-name: release-drafter-config.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} valkey-io-valkey-py-350fd2d/.github/workflows/spellcheck.yml000066400000000000000000000005251504567405200242110ustar00rootroot00000000000000name: spellcheck on: pull_request: jobs: check-spelling: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Check Spelling uses: rojopolis/spellcheck-github-actions@0.51.0 with: config_path: .github/spellcheck-settings.yml task_name: Markdown valkey-io-valkey-py-350fd2d/.gitignore000066400000000000000000000002631504567405200177430ustar00rootroot00000000000000*.pyc valkey.egg-info build/ dist/ dump.rdb _build vagrant/.vagrant .python-version .cache .eggs .idea .coverage env venv coverage.xml .venv* *.xml .coverage* docker/stunnel/keys valkey-io-valkey-py-350fd2d/.isort.cfg000066400000000000000000000001441504567405200176500ustar00rootroot00000000000000[settings] profile=black multi_line_output=3 src_paths = ["valkey", "tests"] skip_glob=benchmarks/* valkey-io-valkey-py-350fd2d/.mypy.ini000066400000000000000000000011641504567405200175310ustar00rootroot00000000000000[mypy] #, docs/examples, tests files = valkey check_untyped_defs = True follow_imports_for_stubs asyncio.= True #disallow_any_decorated = True disallow_subclassing_any = True #disallow_untyped_calls = True disallow_untyped_decorators = True #disallow_untyped_defs = True implicit_reexport = False no_implicit_optional = True show_error_codes = True strict_equality = True warn_incomplete_stub = True warn_redundant_casts = True warn_unreachable = True warn_unused_ignores = True disallow_any_unimported = True #warn_return_any = True [mypy-valkey.asyncio.lock] # TODO: Remove once locks has been rewritten ignore_errors = True valkey-io-valkey-py-350fd2d/.readthedocs.yml000066400000000000000000000003151504567405200210370ustar00rootroot00000000000000version: 2 python: install: - requirements: ./docs/requirements.txt - requirements: requirements.txt build: os: ubuntu-20.04 tools: python: "3.9" sphinx: configuration: docs/conf.py valkey-io-valkey-py-350fd2d/CODE_OF_CONDUCT.md000066400000000000000000000116501504567405200205540ustar00rootroot00000000000000Contributor Covenant Code of Conduct Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at this email address: placeholderkv@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 1. Correction Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 2. Warning Community Impact: A violation through a single incident or series of actions. Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 3. Temporary Ban Community Impact: A serious violation of community standards, including sustained inappropriate behavior. Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 4. Permanent Ban Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. Consequence: A permanent ban from any sort of public interaction within the community. Attribution This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder. For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. valkey-io-valkey-py-350fd2d/CONTRIBUTING.md000066400000000000000000000172271504567405200202140ustar00rootroot00000000000000# Contributing ## Introduction We appreciate your interest in considering contributing to valkey-py. Community contributions mean a lot to us. ## Contributions we need You may already know how you'd like to contribute, whether it's a fix for a bug you encountered, or a new feature your team wants to use. If you don't know where to start, consider improving documentation, bug triaging, and writing tutorials are all examples of helpful contributions that mean less work for you. ## Developer Certificate of Origin We respect the intellectual property rights of others and we want to make sure all incoming contributions are correctly attributed and licensed. A Developer Certificate of Origin (DCO) is a lightweight mechanism to do that. The DCO is a declaration attached to every commit. In the commit message of the contribution, the developer simply adds a `Signed-off-by` statement and thereby agrees to the DCO, which you can find below or at [DeveloperCertificate.org](http://developercertificate.org/). ```text Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as Indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` We require that every contribution to Valkey to be signed with a DCO. We require the usage of known identity (such as a real or preferred name). We do not accept anonymous contributors nor those utilizing pseudonyms. A DCO signed commit will contain a line like: ```text Signed-off-by: Jane Smith ``` You may type this line on your own when writing your commit messages. However, if your user.name and user.email are set in your git configs, you can use `git commit` with `-s` or `--signoff` to add the `Signed-off-by` line to the end of the commit message. We also require revert commits to include a DCO. If you're contributing code to the Valkey project in any other form, including sending a code fragment or patch via private email or public discussion groups, you need to ensure that the contribution is in accordance with the DCO. ## Your First Contribution Unsure where to begin contributing? You can start by looking through [help-wanted issues](https://github.com/valkey-io/valkey-py/issues?q=is%3Aopen+is%3Aissue+label%3ahelp-wanted). Never contributed to open source before? Here are a couple of friendly tutorials: - - ## Getting Started Here's how to get started with your code contribution: 1. Create your own fork of valkey-py 2. Do the changes in your fork 3. *Create a virtualenv and install the development dependencies from the dev_requirements.txt file:* a. python -m venv .venv b. source .venv/bin/activate c. pip install -r dev_requirements.txt c. pip install -r requirements.txt 4. If you need a development environment, run `invoke devenv`. Note: this relies on docker compose to build environments, and assumes that you have a version supporting [docker profiles](https://docs.docker.com/compose/profiles/). 5. While developing, make sure the tests pass by running `invoke tests` 6. If you like the change and think the project could use it, send a pull request To see what else is part of the automation, run `invoke -l` ## The Development Environment Running `invoke devenv` installs the development dependencies specified in the dev_requirements.txt. It starts all of the dockers used by this project, and leaves them running. These can be easily cleaned up with `invoke clean`. NOTE: it is assumed that the user running these tests, can execute docker and its various commands. - A master Valkey node - A Valkey replica node - Three sentinel Valkey nodes - A valkey cluster - An stunnel docker, fronting the master Valkey node The replica node, is a replica of the master node, using the [leader-follower replication](https://redis.io/topics/replication) feature. The sentinels monitor the master node in a [sentinel high-availability configuration](https://redis.io/topics/sentinel). ## Testing Call `invoke tests` to run all tests, or `invoke all-tests` to run linters tests as well. With the 'tests' and 'all-tests' targets, all Valkey and ValkeyCluster tests will be run. It is possible to run only Valkey client tests (with cluster mode disabled) by using `invoke standalone-tests`; similarly, ValkeyCluster tests can be run by using `invoke cluster-tests`. Each run of tests starts and stops the various dockers required. Sometimes things get stuck, an `invoke clean` can help. ## Documentation If relevant, update the code documentation, via docstrings, or in `/docs`. You can check how the documentation looks locally by running `invoke build-docs` and loading the generated HTML files in a browser. Historically there is a mix of styles in the docstrings, but the preferred way of documenting code is by applying the [Google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). Type hints should be added according to PEP484, and should not be repeated in the docstrings. ### Docker Tips Following are a few tips that can help you work with the Docker-based development environment. To get a bash shell inside of a container: `$ docker run -it /bin/bash` Containers run a minimal Debian image that probably lacks tools you want to use. To install packages, first get a bash session (see previous tip) and then run: `$ apt update && apt install ` You can see the logging output of a containers like this: `$ docker logs -f ` ### Troubleshooting If you get any errors when running `make dev` or `make test`, make sure that you are using supported versions of Docker. Please try at least versions of Docker. - Docker 19.03.12 ## How to Report a Bug ### Security Vulnerabilities Reporting a vulnerability? See [SECURITY.md](https://github.com/valkey-io/valkey-py/blob/main/SECURITY.md). ### Everything Else When filing an issue, make sure to answer these five questions: 1. What version of valkey-py are you using? 2. What version of valkey are you using? 3. What did you do? 4. What did you expect to see? 5. What did you see instead? ## Suggest a feature or enhancement If you'd like to contribute a new feature, make sure you check our issue list to see if someone has already proposed it. Work may already be underway on the feature you want or we may have rejected a feature like it already. If you don't see anything, open a new issue that describes the feature you would like and how it should work. ## Code review process The core team regularly looks at pull requests. We will provide feedback as soon as possible. After receiving our feedback, please respond within two weeks. After that time, we may close your PR if it isn't showing any activity. valkey-io-valkey-py-350fd2d/INSTALL000066400000000000000000000001621504567405200170020ustar00rootroot00000000000000 Please use python setup.py install and report errors to via https://github.com/valkey-io/valkey-py/issues/new valkey-io-valkey-py-350fd2d/LICENSE000066400000000000000000000021451504567405200167610ustar00rootroot00000000000000MIT License Copyright (c) 2022-2023, Redis, inc. Copyright (c) 2024-present, valkey-py contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. valkey-io-valkey-py-350fd2d/MANIFEST.in000066400000000000000000000001761504567405200175140ustar00rootroot00000000000000include INSTALL include LICENSE include README.md exclude __pycache__ recursive-include tests * recursive-exclude tests *.pyc valkey-io-valkey-py-350fd2d/README.md000066400000000000000000000144671504567405200172450ustar00rootroot00000000000000# valkey-py The Python interface to the Valkey key-value store. [![CI](https://github.com/valkey-io/valkey-py/workflows/CI/badge.svg)](https://github.com/valkey-io/valkey-py/actions?query=workflow%3ACI+branch%3Amain) [![docs](https://readthedocs.org/projects/valkey-py/badge/?version=latest&style=flat)](https://valkey-py.readthedocs.io/en/latest/) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![pypi](https://badge.fury.io/py/valkey.svg)](https://pypi.org/project/valkey/) [![pre-release](https://img.shields.io/github/v/release/valkey-io/valkey-py?include_prereleases&label=latest-prerelease)](https://github.com/valkey-io/valkey-py/releases) [![codecov](https://codecov.io/gh/valkey-io/valkey-py/branch/main/graph/badge.svg?token=yenl5fzxxr)](https://codecov.io/gh/valkey-io/valkey-py) [Installation](#installation) | [Usage](#usage) | [Documentation](#documentation) | [Advanced Topics](#advanced-topics) | [Contributing](https://github.com/valkey-io/valkey-py/blob/main/CONTRIBUTING.md) --------------------------------------------- ## Installation Start a valkey via docker: ``` bash docker run -p 6379:6379 -it valkey/valkey:latest ``` To install valkey-py, simply: ``` bash $ pip install valkey ``` For faster performance, install valkey with libvalkey support, this provides a compiled response parser, and *for most cases* requires zero code changes. By default, if libvalkey >= 2.3.2 is available, valkey-py will attempt to use it for response parsing. ``` bash $ pip install "valkey[libvalkey]" ``` ## Usage ### Basic Example ``` python >>> import valkey >>> r = valkey.Valkey(host='localhost', port=6379, db=0) >>> r.set('foo', 'bar') True >>> r.get('foo') b'bar' ``` The above code connects to localhost on port 6379, sets a value in Redis, and retrieves it. All responses are returned as bytes in Python, to receive decoded strings, set *decode_responses=True*. For this, and more connection options, see [these examples](https://valkey-py.readthedocs.io/en/latest/examples.html). ### Migration from redis-py You are encouraged to use the new class names, but to allow for a smooth transition alias are available: ``` python >>> import valkey as redis >>> r = redis.Redis(host='localhost', port=6379, db=0) >>> r.set('foo', 'bar') True >>> r.get('foo') b'bar' ``` #### RESP3 Support To enable support for RESP3 change your connection object to include *protocol=3* ``` python >>> import valkey >>> r = valkey.Valkey(host='localhost', port=6379, db=0, protocol=3) ``` ### Connection Pools By default, valkey-py uses a connection pool to manage connections. Each instance of a Valkey class receives its own connection pool. You can however define your own [valkey.ConnectionPool](https://valkey-py.readthedocs.io/en/latest/connections.html#connection-pools). ``` python >>> pool = valkey.ConnectionPool(host='localhost', port=6379, db=0) >>> r = valkey.Valkey(connection_pool=pool) ``` Alternatively, you might want to look at [Async connections](https://valkey-py.readthedocs.io/en/latest/examples/asyncio_examples.html), or [Cluster connections](https://valkey-py.readthedocs.io/en/latest/connections.html#cluster-client), or even [Async Cluster connections](https://valkey-py.readthedocs.io/en/latest/connections.html#async-cluster-client). ### Valkey Commands There is built-in support for all of the [out-of-the-box Valkey commands](https://valkey.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) except where a word (i.e. del) is reserved by the language. See the [complete set of commands](https://github.com/valkey-io/valkey-py/tree/main/valkey/commands), or [the documentation](https://valkey-py.readthedocs.io/en/latest/commands.html). ## Documentation Check out the [documentation](https://valkey-py.readthedocs.io/en/latest/index.html) ## Advanced Topics The [official Valkey command documentation](https://valkey.io/commands) does a great job of explaining each command in detail. valkey-py attempts to adhere to the official command syntax. There are a few exceptions: - **MULTI/EXEC**: These are implemented as part of the Pipeline class. The pipeline is wrapped with the MULTI and EXEC statements by default when it is executed, which can be disabled by specifying transaction=False. See more about Pipelines below. - **SUBSCRIBE/LISTEN**: Similar to pipelines, PubSub is implemented as a separate class as it places the underlying connection in a state where it can\'t execute non-pubsub commands. Calling the pubsub method from the Valkey client will return a PubSub instance where you can subscribe to channels and listen for messages. You can only call PUBLISH from the Valkey client (see [this comment on issue #151](https://github.com/redis/redis-py/issues/151#issuecomment-1545015) for details). For more details, please see the documentation on [advanced topics page](https://valkey-py.readthedocs.io/en/latest/advanced_features.html). ### Pipelines The following is a basic example of a [Valkey pipeline](https://valkey.io/topics/pipelining/), a method to optimize round-trip calls, by batching Valkey commands, and receiving their results as a list. ``` python >>> pipe = r.pipeline() >>> pipe.set('foo', 5) >>> pipe.set('bar', 18.5) >>> pipe.set('blee', "hello world!") >>> pipe.execute() [True, True, True] ``` ### PubSub The following example shows how to utilize [Valkey Pub/Sub](https://valkey.io/topics/pubsub/) to subscribe to specific channels. ``` python >>> r = valkey.Valkey(...) >>> p = r.pubsub() >>> p.subscribe('my-first-channel', 'my-second-channel', ...) >>> p.get_message() {'pattern': None, 'type': 'subscribe', 'channel': b'my-second-channel', 'data': 1} ``` -------------------------- ### Author You can read valkey-py sources on [GitHub](https://github.com/valkey-io/valkey-py), or download it from [pypi](https://pypi.org/project/valkey/) It was created as a fork of [redis-py](https://github.com/redis/redis-py) Special thanks to: - Andy McCurdy () the original author of redis-py. - Ludovico Magnocavallo, author of the original Python Redis client, from which some of the socket code is still used. - Alexander Solovyov for ideas on the generic response callback system. - Paul Hubbard for initial packaging support in redis-py. [![Valkey](./docs/logo-valkey.png)](https://valkey.io/) valkey-io-valkey-py-350fd2d/SECURITY.md000066400000000000000000000007241504567405200175460ustar00rootroot00000000000000## Reporting a Vulnerability If you believe you've discovered a security vulnerability, please contact the Valkey team at security@lists.valkey.io. Please *DO NOT* create an issue. We follow a responsible disclosure procedure, so depending on the severity of the issue we may notify Valkey vendors about the issue before releasing it publicly. If you would like to be added to our list of vendors, please reach out to the Valkey team at maintainers@lists.valkey.io. valkey-io-valkey-py-350fd2d/benchmarks/000077500000000000000000000000001504567405200200675ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/benchmarks/__init__.py000066400000000000000000000000001504567405200221660ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/benchmarks/base.py000066400000000000000000000025171504567405200213600ustar00rootroot00000000000000import functools import itertools import sys import timeit import valkey class Benchmark: ARGUMENTS = () def __init__(self): self._client = None def get_client(self, **kwargs): # eventually make this more robust and take optional args from # argparse if self._client is None or kwargs: defaults = {"db": 9} defaults.update(kwargs) pool = valkey.ConnectionPool(**kwargs) self._client = valkey.Valkey(connection_pool=pool) return self._client def setup(self, **kwargs): pass def run(self, **kwargs): pass def run_benchmark(self): group_names = [group["name"] for group in self.ARGUMENTS] group_values = [group["values"] for group in self.ARGUMENTS] for value_set in itertools.product(*group_values): pairs = list(zip(group_names, value_set)) arg_string = ", ".join(f"{p[0]}={p[1]}" for p in pairs) sys.stdout.write(f"Benchmark: {arg_string}... ") sys.stdout.flush() kwargs = dict(pairs) setup = functools.partial(self.setup, **kwargs) run = functools.partial(self.run, **kwargs) t = timeit.timeit(stmt=run, setup=setup, number=1000) sys.stdout.write(f"{t:f}\n") sys.stdout.flush() valkey-io-valkey-py-350fd2d/benchmarks/basic_operations.py000066400000000000000000000114031504567405200237640ustar00rootroot00000000000000import time from argparse import ArgumentParser from functools import wraps import valkey def parse_args(): parser = ArgumentParser() parser.add_argument( "-n", type=int, help="Total number of requests (default 100000)", default=100000 ) parser.add_argument( "-P", type=int, help=("Pipeline requests. Default 1 (no pipeline)."), default=1, ) parser.add_argument( "-s", type=int, help="Data size of SET/GET value in bytes (default 2)", default=2, ) args = parser.parse_args() return args def run(): args = parse_args() r = valkey.Valkey() r.flushall() set_str(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) set_int(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) get_str(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) get_int(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) incr(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) lpush(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) lrange_300(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) lpop(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) hmset(conn=r, num=args.n, pipeline_size=args.P, data_size=args.s) def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.monotonic() ret = func(*args, **kwargs) duration = time.monotonic() - start if "num" in kwargs: count = kwargs["num"] else: count = args[1] print(f"{func.__name__} - {count} Requests") print(f"Duration = {duration}") print(f"Rate = {count/duration}") print() return ret return wrapper @timer def set_str(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() set_data = "a".ljust(data_size, "0") for i in range(num): conn.set(f"set_str:{i}", set_data) if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def set_int(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() set_data = 10 ** (data_size - 1) for i in range(num): conn.set(f"set_int:{i}", set_data) if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def get_str(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() for i in range(num): conn.get(f"set_str:{i}") if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def get_int(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() for i in range(num): conn.get(f"set_int:{i}") if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def incr(conn, num, pipeline_size, *args, **kwargs): if pipeline_size > 1: conn = conn.pipeline() for i in range(num): conn.incr("incr_key") if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def lpush(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() set_data = 10 ** (data_size - 1) for i in range(num): conn.lpush("lpush_key", set_data) if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def lrange_300(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() for i in range(num): conn.lrange("lpush_key", i, i + 300) if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def lpop(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() for i in range(num): conn.lpop("lpush_key") if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() @timer def hmset(conn, num, pipeline_size, data_size): if pipeline_size > 1: conn = conn.pipeline() set_data = {"str_value": "string", "int_value": 123456, "float_value": 123456.0} for i in range(num): conn.hmset("hmset_key", set_data) if pipeline_size > 1 and i % pipeline_size == 0: conn.execute() if pipeline_size > 1: conn.execute() if __name__ == "__main__": run() valkey-io-valkey-py-350fd2d/benchmarks/cluster_async.py000066400000000000000000000161171504567405200233250ustar00rootroot00000000000000import asyncio import functools import time import aiovalkey_cluster import avalkey import uvloop import valkey.asyncio as valkeypy def timer(func): @functools.wraps(func) async def wrapper(*args, **kwargs): tic = time.perf_counter() await func(*args, **kwargs) toc = time.perf_counter() return f"{toc - tic:.4f}" return wrapper @timer async def set_str(client, gather, data): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.set(f"bench:str_{i}", data)) for i in range(100) ) ) else: for i in range(count): await client.set(f"bench:str_{i}", data) @timer async def set_int(client, gather, data): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.set(f"bench:int_{i}", data)) for i in range(100) ) ) else: for i in range(count): await client.set(f"bench:int_{i}", data) @timer async def get_str(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *(asyncio.create_task(client.get(f"bench:str_{i}")) for i in range(100)) ) else: for i in range(count): await client.get(f"bench:str_{i}") @timer async def get_int(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *(asyncio.create_task(client.get(f"bench:int_{i}")) for i in range(100)) ) else: for i in range(count): await client.get(f"bench:int_{i}") @timer async def hset(client, gather, data): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.hset("bench:hset", str(i), data)) for i in range(100) ) ) else: for i in range(count): await client.hset("bench:hset", str(i), data) @timer async def hget(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.hget("bench:hset", str(i))) for i in range(100) ) ) else: for i in range(count): await client.hget("bench:hset", str(i)) @timer async def incr(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *(asyncio.create_task(client.incr("bench:incr")) for i in range(100)) ) else: for i in range(count): await client.incr("bench:incr") @timer async def lpush(client, gather, data): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.lpush("bench:lpush", data)) for i in range(100) ) ) else: for i in range(count): await client.lpush("bench:lpush", data) @timer async def lrange_300(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *( asyncio.create_task(client.lrange("bench:lpush", i, i + 300)) for i in range(100) ) ) else: for i in range(count): await client.lrange("bench:lpush", i, i + 300) @timer async def lpop(client, gather): if gather: for _ in range(count // 100): await asyncio.gather( *(asyncio.create_task(client.lpop("bench:lpush")) for i in range(100)) ) else: for i in range(count): await client.lpop("bench:lpush") @timer async def warmup(client): await asyncio.gather( *(asyncio.create_task(client.exists(f"bench:warmup_{i}")) for i in range(100)) ) @timer async def run(client, gather): data_str = "a" * size data_int = int("1" * size) if gather is False: for ret in await asyncio.gather( asyncio.create_task(set_str(client, gather, data_str)), asyncio.create_task(set_int(client, gather, data_int)), asyncio.create_task(hset(client, gather, data_str)), asyncio.create_task(incr(client, gather)), asyncio.create_task(lpush(client, gather, data_int)), ): print(ret) for ret in await asyncio.gather( asyncio.create_task(get_str(client, gather)), asyncio.create_task(get_int(client, gather)), asyncio.create_task(hget(client, gather)), asyncio.create_task(lrange_300(client, gather)), asyncio.create_task(lpop(client, gather)), ): print(ret) else: print(await set_str(client, gather, data_str)) print(await set_int(client, gather, data_int)) print(await hset(client, gather, data_str)) print(await incr(client, gather)) print(await lpush(client, gather, data_int)) print(await get_str(client, gather)) print(await get_int(client, gather)) print(await hget(client, gather)) print(await lrange_300(client, gather)) print(await lpop(client, gather)) async def main(loop, gather=None): arc = avalkey.StrictValkeyCluster( host=host, port=port, password=password, max_connections=2**31, max_connections_per_node=2**31, readonly=False, reinitialize_steps=count, skip_full_coverage_check=True, decode_responses=False, max_idle_time=count, idle_check_interval=count, ) print(f"{loop} {gather} {await warmup(arc)} avalkey") print(await run(arc, gather=gather)) arc.connection_pool.disconnect() aiorc = await aiovalkey_cluster.create_valkey_cluster( [(host, port)], password=password, state_reload_interval=count, idle_connection_timeout=count, pool_maxsize=2**31, ) print(f"{loop} {gather} {await warmup(aiorc)} aiovalkey-cluster") print(await run(aiorc, gather=gather)) aiorc.close() await aiorc.wait_closed() async with valkeypy.ValkeyCluster( host=host, port=port, password=password, reinitialize_steps=count, read_from_replicas=False, decode_responses=False, max_connections=2**31, ) as rca: print(f"{loop} {gather} {await warmup(rca)} valkeypy") print(await run(rca, gather=gather)) if __name__ == "__main__": host = "localhost" port = 16379 password = None count = 10000 size = 256 asyncio.run(main("asyncio")) asyncio.run(main("asyncio", gather=False)) asyncio.run(main("asyncio", gather=True)) uvloop.install() asyncio.run(main("uvloop")) asyncio.run(main("uvloop", gather=False)) asyncio.run(main("uvloop", gather=True)) valkey-io-valkey-py-350fd2d/benchmarks/cluster_async_pipeline.py000066400000000000000000000050621504567405200252070ustar00rootroot00000000000000import asyncio import functools import time import aiovalkey_cluster import avalkey import uvloop import valkey.asyncio as valkeypy def timer(func): @functools.wraps(func) async def wrapper(*args, **kwargs): tic = time.perf_counter() await func(*args, **kwargs) toc = time.perf_counter() return f"{toc - tic:.4f}" return wrapper @timer async def warmup(client): await asyncio.gather( *(asyncio.create_task(client.exists(f"bench:warmup_{i}")) for i in range(100)) ) @timer async def run(client): data_str = "a" * size data_int = int("1" * size) for i in range(count): with client.pipeline() as pipe: await ( pipe.set(f"bench:str_{i}", data_str) .set(f"bench:int_{i}", data_int) .get(f"bench:str_{i}") .get(f"bench:int_{i}") .hset("bench:hset", str(i), data_str) .hget("bench:hset", str(i)) .incr("bench:incr") .lpush("bench:lpush", data_int) .lrange("bench:lpush", 0, 300) .lpop("bench:lpush") .execute() ) async def main(loop): arc = avalkey.StrictValkeyCluster( host=host, port=port, password=password, max_connections=2**31, max_connections_per_node=2**31, readonly=False, reinitialize_steps=count, skip_full_coverage_check=True, decode_responses=False, max_idle_time=count, idle_check_interval=count, ) print(f"{loop} {await warmup(arc)} avalkey") print(await run(arc)) arc.connection_pool.disconnect() aiorc = await aiovalkey_cluster.create_valkey_cluster( [(host, port)], password=password, state_reload_interval=count, idle_connection_timeout=count, pool_maxsize=2**31, ) print(f"{loop} {await warmup(aiorc)} aiovalkey-cluster") print(await run(aiorc)) aiorc.close() await aiorc.wait_closed() async with valkeypy.ValkeyCluster( host=host, port=port, password=password, reinitialize_steps=count, read_from_replicas=False, decode_responses=False, max_connections=2**31, ) as rca: print(f"{loop} {await warmup(rca)} valkeypy") print(await run(rca)) if __name__ == "__main__": host = "localhost" port = 16379 password = None count = 10000 size = 256 asyncio.run(main("asyncio")) uvloop.install() asyncio.run(main("uvloop")) valkey-io-valkey-py-350fd2d/benchmarks/command_packer_benchmark.py000066400000000000000000000063151504567405200254230ustar00rootroot00000000000000from base import Benchmark from valkey.connection import SYM_CRLF, SYM_DOLLAR, SYM_EMPTY, SYM_STAR, Connection class StringJoiningConnection(Connection): def send_packed_command(self, command, check_health=True): "Send an already packed command to the Valkey server" if not self._sock: self.connect() try: self._sock.sendall(command) except OSError as e: self.disconnect() if len(e.args) == 1: _errno, errmsg = "UNKNOWN", e.args[0] else: _errno, errmsg = e.args raise ConnectionError(f"Error {_errno} while writing to socket. {errmsg}.") except Exception: self.disconnect() raise def pack_command(self, *args): "Pack a series of arguments into a value Valkey command" args_output = SYM_EMPTY.join( [ SYM_EMPTY.join( (SYM_DOLLAR, str(len(k)).encode(), SYM_CRLF, k, SYM_CRLF) ) for k in map(self.encoder.encode, args) ] ) output = SYM_EMPTY.join( (SYM_STAR, str(len(args)).encode(), SYM_CRLF, args_output) ) return output class ListJoiningConnection(Connection): def send_packed_command(self, command, check_health=True): if not self._sock: self.connect() try: if isinstance(command, str): command = [command] for item in command: self._sock.sendall(item) except OSError as e: self.disconnect() if len(e.args) == 1: _errno, errmsg = "UNKNOWN", e.args[0] else: _errno, errmsg = e.args raise ConnectionError(f"Error {_errno} while writing to socket. {errmsg}.") except Exception: self.disconnect() raise def pack_command(self, *args): output = [] buff = SYM_EMPTY.join((SYM_STAR, str(len(args)).encode(), SYM_CRLF)) for k in map(self.encoder.encode, args): if len(buff) > 6000 or len(k) > 6000: buff = SYM_EMPTY.join( (buff, SYM_DOLLAR, str(len(k)).encode(), SYM_CRLF) ) output.append(buff) output.append(k) buff = SYM_CRLF else: buff = SYM_EMPTY.join( (buff, SYM_DOLLAR, str(len(k)).encode(), SYM_CRLF, k, SYM_CRLF) ) output.append(buff) return output class CommandPackerBenchmark(Benchmark): ARGUMENTS = ( { "name": "connection_class", "values": [StringJoiningConnection, ListJoiningConnection], }, { "name": "value_size", "values": [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000], }, ) def setup(self, connection_class, value_size): self.get_client(connection_class=connection_class) def run(self, connection_class, value_size): r = self.get_client() x = "a" * value_size r.set("benchmark", x) if __name__ == "__main__": CommandPackerBenchmark().run_benchmark() valkey-io-valkey-py-350fd2d/benchmarks/socket_read_size.py000066400000000000000000000014431504567405200237600ustar00rootroot00000000000000from base import Benchmark from valkey.connection import PythonParser, _LibvalkeyParser class SocketReadBenchmark(Benchmark): ARGUMENTS = ( {"name": "parser", "values": [PythonParser, _LibvalkeyParser]}, { "name": "value_size", "values": [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000], }, {"name": "read_size", "values": [4096, 8192, 16384, 32768, 65536, 131072]}, ) def setup(self, value_size, read_size, parser): r = self.get_client(parser_class=parser, socket_read_size=read_size) r.set("benchmark", "a" * value_size) def run(self, value_size, read_size, parser): r = self.get_client() r.get("benchmark") if __name__ == "__main__": SocketReadBenchmark().run_benchmark() valkey-io-valkey-py-350fd2d/codecov.yml000066400000000000000000000003201504567405200201120ustar00rootroot00000000000000ignore: - "benchmarks/**" - "tasks.py" codecov: require_ci_to_pass: yes coverage: precision: 2 round: down range: "80...100" status: patch: off # off for now as it yells about everything valkey-io-valkey-py-350fd2d/compose.yaml000066400000000000000000000042261504567405200203070ustar00rootroot00000000000000--- services: valkey: image: valkey/valkey:latest container_name: valkey-standalone ports: - 6379:6379 entrypoint: "/usr/local/bin/docker-entrypoint.sh --enable-debug-command yes --enable-module-command yes" profiles: - standalone - sentinel - replica - all replica: image: valkey/valkey:latest container_name: valkey-replica depends_on: - valkey entrypoint: "/usr/local/bin/docker-entrypoint.sh --replicaof valkey 6379" ports: - 6380:6379 profiles: - replica - all cluster: container_name: valkey-cluster build: context: . dockerfile: dockers/Dockerfile.cluster ports: - 16379:16379 - 16380:16380 - 16381:16381 - 16382:16382 - 16383:16383 - 16384:16384 volumes: - "./dockers/cluster.valkey.conf:/valkey.conf:ro" profiles: - cluster - all stunnel: build: context: ./dockers/stunnel depends_on: - valkey ports: - "6666:6666" profiles: - all - standalone - ssl volumes: - "./dockers/stunnel/conf:/etc/stunnel/conf.d:ro" - "./dockers/stunnel/keys:/etc/stunnel/keys:ro" sentinel: image: valkey/valkey:latest container_name: valkey-sentinel depends_on: - valkey entrypoint: "/usr/local/bin/valkey-sentinel /valkey.conf --port 26379" ports: - 26379:26379 volumes: - "./dockers/sentinel.conf:/valkey.conf" profiles: - sentinel - all sentinel2: image: valkey/valkey:latest container_name: valkey-sentinel2 depends_on: - valkey entrypoint: "/usr/local/bin/valkey-sentinel /valkey.conf --port 26380" ports: - 26380:26380 volumes: - "./dockers/sentinel.conf:/valkey.conf" profiles: - sentinel - all sentinel3: image: valkey/valkey:latest container_name: valkey-sentinel3 depends_on: - valkey entrypoint: "/usr/local/bin/valkey-sentinel /valkey.conf --port 26381" ports: - 26381:26381 volumes: - "./dockers/sentinel.conf:/valkey.conf" profiles: - sentinel - all valkey-io-valkey-py-350fd2d/dev_requirements.txt000066400000000000000000000002561504567405200220770ustar00rootroot00000000000000black cachetools click flake8-isort flake8 flynt invoke mock packaging>=20.4 pytest pytest-asyncio pytest-cov pytest-timeout ujson>=4.2.0 uvloop vulture>=2.3.0 wheel>=0.30.0 valkey-io-valkey-py-350fd2d/dockers/000077500000000000000000000000001504567405200174045ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/dockers/Dockerfile.cluster000066400000000000000000000002311504567405200230520ustar00rootroot00000000000000FROM valkey/valkey:latest as rss COPY dockers/create_cluster.sh /create_cluster.sh RUN chmod a+x /create_cluster.sh ENTRYPOINT [ "/create_cluster.sh"] valkey-io-valkey-py-350fd2d/dockers/cluster.valkey.conf000066400000000000000000000000531504567405200232240ustar00rootroot00000000000000protected-mode no enable-debug-command yes valkey-io-valkey-py-350fd2d/dockers/create_cluster.sh000066400000000000000000000021771504567405200227530ustar00rootroot00000000000000#! /bin/bash mkdir -p /nodes touch /nodes/nodemap if [ -z ${START_PORT} ]; then START_PORT=16379 fi if [ -z ${END_PORT} ]; then END_PORT=16384 fi if [ ! -z "$3" ]; then START_PORT=$2 START_PORT=$3 fi echo "STARTING: ${START_PORT}" echo "ENDING: ${END_PORT}" for PORT in `seq ${START_PORT} ${END_PORT}`; do mkdir -p /nodes/$PORT if [[ -e /valkey.conf ]]; then cp /valkey.conf /nodes/$PORT/valkey.conf else touch /nodes/$PORT/valkey.conf fi cat << EOF >> /nodes/$PORT/valkey.conf port ${PORT} cluster-enabled yes daemonize yes logfile /valkey.log dir /nodes/$PORT EOF set -x /usr/local/bin/valkey-server /nodes/$PORT/valkey.conf sleep 1 if [ $? -ne 0 ]; then echo "Valkey failed to start, exiting." continue fi echo 127.0.0.1:$PORT >> /nodes/nodemap done if [ -z "${VALKEY_PASSWORD}" ]; then echo yes | /usr/local/bin/valkey-cli --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1 else echo yes | /usr/local/bin/valkey-cli -a ${VALKEY_PASSWORD} --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1 fi tail -f /valkey.log valkey-io-valkey-py-350fd2d/dockers/sentinel.conf000066400000000000000000000003311504567405200220710ustar00rootroot00000000000000sentinel resolve-hostnames yes sentinel monitor valkey-py-test valkey 6379 2 sentinel down-after-milliseconds valkey-py-test 5000 sentinel failover-timeout valkey-py-test 60000 sentinel parallel-syncs valkey-py-test 1valkey-io-valkey-py-350fd2d/dockers/stunnel/000077500000000000000000000000001504567405200210745ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/dockers/stunnel/Dockerfile000066400000000000000000000003421504567405200230650ustar00rootroot00000000000000FROM ubuntu:latest MAINTAINER ahmedsobeh RUN apt-get update && apt-get install -y stunnel COPY conf /etc/stunnel/conf COPY keys /etc/stunnel/keys CMD ["stunnel", "/etc/stunnel/conf/stunnel.conf"] valkey-io-valkey-py-350fd2d/dockers/stunnel/README000066400000000000000000000003011504567405200217460ustar00rootroot00000000000000 This directory contains a helper script to create ssl certificates for ssl tests. If the certificates are out of date, re-run create_certs and check them in. These are snake oil certificates. valkey-io-valkey-py-350fd2d/dockers/stunnel/conf/000077500000000000000000000000001504567405200220215ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/dockers/stunnel/conf/stunnel.conf000066400000000000000000000002321504567405200243550ustar00rootroot00000000000000foreground = yes [valkey] accept = 6666 connect = valkey:6379 cert = /etc/stunnel/keys/server-cert.pem key = /etc/stunnel/keys/server-key.pem verify = 0 valkey-io-valkey-py-350fd2d/dockers/stunnel/create_certs.sh000077500000000000000000000023601504567405200240770ustar00rootroot00000000000000#!/bin/bash set -e CONFIG_FILE=$(realpath "$(dirname "$0")")/openssl.cnf DESTDIR=$(dirname "$0")/keys test -d ${DESTDIR} || mkdir ${DESTDIR} cd ${DESTDIR} which openssl &>/dev/null if [ $? -ne 0 ]; then echo "No openssl binary present, exiting." exit 1 fi openssl genrsa -out ca-key.pem 2048 &>/dev/null openssl req -new -x509 -nodes -days 365000 \ -key ca-key.pem \ -out ca-cert.pem \ -config "$CONFIG_FILE" \ -extensions v3_ca \ -subj "/CN=valkey-py-ca" openssl req -newkey rsa:2048 -nodes \ -keyout server-key.pem \ -out server-req.pem \ -config "$CONFIG_FILE" \ -extensions v3_req \ -subj "/CN=valkey-py-server" openssl x509 -req -days 365000 -set_serial 01 \ -in server-req.pem \ -out server-cert.pem \ -CA ca-cert.pem \ -CAkey ca-key.pem \ -extfile "$CONFIG_FILE" \ -extensions v3_req openssl req -newkey rsa:2048 -nodes \ -keyout client-key.pem \ -out client-req.pem \ -config "$CONFIG_FILE" \ -extensions v3_req \ -subj "/CN=valkey-py-client" openssl x509 -req -days 365000 -set_serial 01 \ -in client-req.pem \ -out client-cert.pem \ -CA ca-cert.pem \ -CAkey ca-key.pem \ -extfile "$CONFIG_FILE" \ -extensions v3_req echo "Keys generated in ${DESTDIR}:" ls valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/000077500000000000000000000000001504567405200220475ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/ca-cert.pem000066400000000000000000000021131504567405200240650ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC/TCCAeWgAwIBAgIUL0/OSD+P0ZISmuNtnbVNjymQn3wwDQYJKoZIhvcNAQEL BQAwFzEVMBMGA1UEAwwMdmFsa2V5LXB5LWNhMCAXDTI0MTEwMTExNTEwMFoYDzMw MjQwMzA0MTE1MTAwWjAXMRUwEwYDVQQDDAx2YWxrZXktcHktY2EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaENi99I937j1QW4QOM7YSPHzymMHJpnRO ZP9JYDxOO7XjKpRwhyU4hM3QfxeNJi04VKv+FZe8QswCSqyp6OeNFPAuQ2M3Shcl neUymoSVsQqyqzrJ8G4qW3sAMdvG32rA8sRsOewSVABnsi0wUZS+0+4EMR+L372O WDd9ZV88uePwsY6MTfqvxoyh0S+5E3xdyep956+LGotr+maDZ/MrEP2Kl1StWv4W mS0Gd7bzJaGsCazGXfc22JLwztBG/JgZdjI6T3e1ION0VpaQ82uMqvFmajmPxWUU 8lbjAzeHSGOJq+BZmPVh6NFp6Pn1xdH8OOHW1CW8UMaAjQre37bHAgMBAAGjPzA9 MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQCiVq1mATQ GX/9xPxG9l0soukgFzANBgkqhkiG9w0BAQsFAAOCAQEALUxF0RNlfpj55H2ku7r6 aYcKsElzmCdgICxc0jrhvlMT7yv03nt0EOxgx4yWeoCNNKcAhAy9rHh+3pfyXwS7 RAkwvwTxbqfdXB/mviolrPus0fn8dfC0ZpVSS8DYxS54ziFU0BkZi+odlkBA5PBE p6p7kWwx6hc1h+F6abrNEivLe7G5V1Z8sIBNkj9Xj36muDXwNJjCOTq2FyeRRV4H C9ztHK4iVhlw2UYHZ8dQjyI/MSPrAyMVbmbglhIdGGoE+JGAixWkB02kjySQ6lxh Yt7b7icD4hmHxnXoxoN31wNF4YMePMZmQsuQEjjndSg5Nt+Vbk1Bk/jK88p297vi gQ== -----END CERTIFICATE----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/ca-key.pem000066400000000000000000000032501504567405200237230ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDaENi99I937j1Q W4QOM7YSPHzymMHJpnROZP9JYDxOO7XjKpRwhyU4hM3QfxeNJi04VKv+FZe8QswC Sqyp6OeNFPAuQ2M3ShclneUymoSVsQqyqzrJ8G4qW3sAMdvG32rA8sRsOewSVABn si0wUZS+0+4EMR+L372OWDd9ZV88uePwsY6MTfqvxoyh0S+5E3xdyep956+LGotr +maDZ/MrEP2Kl1StWv4WmS0Gd7bzJaGsCazGXfc22JLwztBG/JgZdjI6T3e1ION0 VpaQ82uMqvFmajmPxWUU8lbjAzeHSGOJq+BZmPVh6NFp6Pn1xdH8OOHW1CW8UMaA jQre37bHAgMBAAECggEAUbk4kVADKI4nemMhxXTJymHS7dQj5B+2vN6K8gPX9fXY v67ofJeZcmoK/BV1TRe+oLrSzmFnQU3DSSSVOwQnKy9qp9vnZgQlUpqvF9zizXrR KI6VdLLfho5MNZF57Tkzt+YDiQ/YEjJbCIG0/8PDPBUOwZFrYi9SyfLzsNH59DaB Nf64J6KpMLMEP8BzDf9MkDWjg/uZZ5rJ2VDkl11QZCmyAAPMXps1nH4WJojVEwB7 ul/VK8wrqiiyZqzDesw/jcET7DrCHtix35An8NJZAtWPILgHHnAlLPmG7a0uyy3Y XaeqZRppUkuSv/OKf3Q0l/2IzjcNb3tjbktVSCXgGQKBgQDyxozi0V09Bc9w7yI3 DaREFSs0h134ByzvsoObJMZTc9Qkis8VZB+IhMO4RaP2DNstJVqkl0pQWCr/C5ln d6tYUtueeQ/9SYusnLIxu+HtsySPzBKthLrWArPQ6U1q70irxNovcSxOWimuSUIA ftzWV6mCdBUsCImGZaiKl7GqDQKBgQDl8blf5iVRArHA/8vTBwdBNf3tkuctFcE2 Pqmg5KQmGEvIO0S/DB2zAY+4JF4E4VrdJL47xXTnf+XN2ptQUf5kjwLflEaimupv knwtNG+fq6hcWMeN+hnf0+A81b03Klo3H2JsuQ3EZ8kXOrpF8t/PanXz9UuV8Bkl IjDwBLCTIwKBgQC9cIdRGjPaQSVsp30YXnG2mpobJCIEP30mETM2pYyIZBK+7P3I YFdmzMp4iQb3IXMJmGNRmahoZ1QtrhxnK28tvYIX97mtWG1AJQm7WzNhqu81sfVF JxQvmO49bz902QDo3/OtH2+GOD7b+9gf0N579u2TmQdIU+UUVVEdzF7bJQKBgFQX TWKryNPSd20MXt7iwB1yAFYEljRfs1QCIIitdPZVhklIm4B+jtHq7UM7UYLZYyBi kotLT9BlboYUvx3ljnH59uQK1rYaj0eUO4NQnM24ug5jjT73ysSXOHcm91aYT3u/ J4B5QHamOd0b5gk0o/K3jUFVYHoJ3zg8Q8dS/7wfAoGBAMTue1Uq2GZOklxHWGUf AedLR0aeNrV01hvl/R+sVb0h/lPqSeg5jQeLUHvkgG4SIq93dNhCnzMI6Dhch+Yc o337l8S4ZcmJblp0uDz2gg2BLpt3PUPDWYQy8oFAjGK4JVwNgxPzDchMXGPDHuhQ 8r+9yBZlU1k64S3EIYuK4m7a -----END PRIVATE KEY----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/client-cert.pem000066400000000000000000000021371504567405200247660ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAx2YWxr ZXktcHktY2EwIBcNMjQxMTAxMTE1MTAwWhgPMzAyNDAzMDQxMTUxMDBaMBsxGTAX BgNVBAMMEHZhbGtleS1weS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQCywTxWqxdElzelLCtjgek1JX5H3D2FwtnlNzqHJ8Ork4zjiBkouPoC PAHWRV2VH6Y9vUkFzoJYCtjNBs7dawXDyl4Jj2PsbMIFpD5gRjztqX0yFVupWrVz dovQheEzDC5pOie8vhgD3s5ej0pus1C1dQJ3KhAa49Ci88+cAO8kO7/MnjoY97SJ vsk0Ui2zcBPGzXHZnHcDkyjegDBztuNKuhnrP90zLMWylwVH+h6QpAi7JnnPfpUr bDgraBets8tco//Lr745M1vcV7jCxFk+9eyBhkAlbq/Z+FsA/i47vLqqwy93yt9S 61XkElyFUbcYbRS2xF58WS//18Dv2js5AgMBAAGjXTBbMAwGA1UdEwEB/wQCMAAw CwYDVR0PBAQDAgWgMB0GA1UdDgQWBBRKmRR43Y3FSUupi7emhzJIqyn2ZzAfBgNV HSMEGDAWgBQCiVq1mATQGX/9xPxG9l0soukgFzANBgkqhkiG9w0BAQsFAAOCAQEA PhwfN23MKSKIOgg+heNiz9HWuNxacjlHp4sbgM/vHvah1x7nctdEsXPm1NO6J3uu iTGIEV8u4I3Pry2TRsP5UZKX5VMTfB9TeonxYbu51P+lAIu+fB5fwQ3qHaycq6su yKHIzDHP7+oOSd3lHTUiyIa04h1EevMjoWmihsFOgHQCNRaU3ifdyzcPa4Exd4dL MyuXq9ccbuqHe+UZyj8ftt8zYtIILcAnLJlhosIl+VsSWyD1e0WRfR36/tLR8ACf +nz6aEXaCk07BgryllC0+YvoIVzfXddfD/p6e8/CO2Vxw+df6OT3Z6sZrRnFVK+m 6PAcuS8VDO7k2y457d8w0A== -----END CERTIFICATE----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/client-key.pem000066400000000000000000000032501504567405200246160ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCywTxWqxdElzel LCtjgek1JX5H3D2FwtnlNzqHJ8Ork4zjiBkouPoCPAHWRV2VH6Y9vUkFzoJYCtjN Bs7dawXDyl4Jj2PsbMIFpD5gRjztqX0yFVupWrVzdovQheEzDC5pOie8vhgD3s5e j0pus1C1dQJ3KhAa49Ci88+cAO8kO7/MnjoY97SJvsk0Ui2zcBPGzXHZnHcDkyje gDBztuNKuhnrP90zLMWylwVH+h6QpAi7JnnPfpUrbDgraBets8tco//Lr745M1vc V7jCxFk+9eyBhkAlbq/Z+FsA/i47vLqqwy93yt9S61XkElyFUbcYbRS2xF58WS// 18Dv2js5AgMBAAECggEAIajCSF2SFY/V4gvFpcieFaxYMYbWrNvKdN9n7XA+541y n5uOhT0Dkq0i+Wp5Wy2o+4IrgGTo5VQxi7XG+SmAXeQ6vdkayzeVd0N8nVtMeMIL +YTNDEAw36uIWz0CcT7PdHAHcIJo+j2XpXWc4ehw/6InUzH/81hHfo+jXbBNV4h6 B9+lx2SjO985i9ubBsHrvf4CbjIElnOD0fHgdTstvDi03U3U2J75ASiYri6ei5Ob jOUMqhGVAPHlonCk23uWqOvqgG0Y/XqnzCWfoJxRI4IU689/jGNsYOw8ZYhG4A/u nlbsQ4NTnJ3jCimtdWAsApYoNvXJ82cwKJcyB2LPaQKBgQDgPekDTt1UXEUDTjoW UGBIhXfFkliV4Bxfj5TKvTfX5xP3dlU+IogaqVL7wIfdlEkLtNB8tnBxIas/8XpH bF+/w3TqBRfC7NW6qOs0mO7rgiWWDJX5nYW3dgViMOVCZiRTijiOcXVrhHFfXoLT 7F7xMZQYEFdMxXaP9QsRIMEXnQKBgQDMEicpWJb54qPYUYHMYyf4z/hSyrcMVGNl EhozCqzpZrB0C59ohzadZ3nKQyitlIkPSlhneWjF20mnovF4AS0qsckbDv2Z7nOS ZKxnfUfJ/i0BenVVv96U/tD5oHFzf2ezbk1bfWVpry7dKQjoh7zmxDCrJEi58Igq pwqTevtVTQKBgQDIpJyp6RcBNM5LduNis+hy+3l/vsKk2DKLDt4Dyer9tDWZZrg/ MIa31Gn7+PmYueXiI5eo/1T85TNls5vF7KJ/41PpUUVBlMhojFxoY67j6z/WUsye 3OOYlHGcukNodhxq43JXgg2edpM60kYdeZI6HjJ0laqHdufvR0LvwG8FwQKBgAyn k4Yc2D/mrgJcC5CBFZl4TA3WREOfeApsdPN1VgOjOo33qorw15IrOIIyZ/NboqQw GAtSnAyo7IhYsmCesg5TuATViSRihQgu9gH04t7DxEazMVN/8m2K36qbKG3hGK0n yeRCgmdrVZyhTswcnrowsFPsjBX7tHXwpdc/aRaBAoGAKeYeOxGwx3L25g6/VzqU 8d/Uu2t39crLz/8cElqnjoN2Lis0m6FezUiIYCKHgfQtFtypdrFI6UjWk+G4mS5M zS2j3B+66bfbBLgZrbav30lLz8YoKAuX1OIPsq19e2YIqb2sA3J4DqjaX73fFndW ekKsHsxJCHDmI2QXsu5B9ZM= -----END PRIVATE KEY----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/client-req.pem000066400000000000000000000017001504567405200246130ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICjDCCAXQCAQAwGzEZMBcGA1UEAwwQdmFsa2V5LXB5LWNsaWVudDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBALLBPFarF0SXN6UsK2OB6TUlfkfcPYXC 2eU3Oocnw6uTjOOIGSi4+gI8AdZFXZUfpj29SQXOglgK2M0Gzt1rBcPKXgmPY+xs wgWkPmBGPO2pfTIVW6latXN2i9CF4TMMLmk6J7y+GAPezl6PSm6zULV1AncqEBrj 0KLzz5wA7yQ7v8yeOhj3tIm+yTRSLbNwE8bNcdmcdwOTKN6AMHO240q6Ges/3TMs xbKXBUf6HpCkCLsmec9+lStsOCtoF62zy1yj/8uvvjkzW9xXuMLEWT717IGGQCVu r9n4WwD+Lju8uqrDL3fK31LrVeQSXIVRtxhtFLbEXnxZL//XwO/aOzkCAwEAAaAs MCoGCSqGSIb3DQEJDjEdMBswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBaAwDQYJ KoZIhvcNAQELBQADggEBAHtRC+8mMMebz3cbeZa8ORnk7zqhkKvKjKXND01LVwlj spDPLYks4ySd5pehpsopxtF0DQw4EDnGq4f7MnwJvArSc1uqoul1seHKffesDKmY zIbumivBfHUaIrqlxIcyXB75aM0rV7XD+DTTVX+39XCavckXpYHhDLI2slR6P+71 OLhCV3GEmhJchyNjr/tMidtO/5NkcIFjcanZYf0wYWHo+lVBEmkwQBHL132TJge3 XCTSfoL5m1smokq+zrJDaJjtsYfR2kUzU6MMY8H2omI7DMwEISJEpYK5FumxTWxx djEFXUcRybmtRcnwHNJXFpSNANfWaSx0oCxi51BN808= -----END CERTIFICATE REQUEST----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/server-cert.pem000066400000000000000000000021371504567405200250160ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAx2YWxr ZXktcHktY2EwIBcNMjQxMTAxMTE1MTAwWhgPMzAyNDAzMDQxMTUxMDBaMBsxGTAX BgNVBAMMEHZhbGtleS1weS1zZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQCzjC1+i1/DoFk1wNOQENIC15+kL/FKk5r1JHCB6h3pOgKre1eAwVIC OQ2EgD76OarvQNK4ECydOacY09uCjR+BIWn65fDpC8jGlfZmgGr3IiLoIyKkoqXv 8ZUxU/lTcFs4TKjCioXiYXwcCnVW7mG95I37IDtul/bUh+aySE7T2b+6tdwUKsQG HF1PULH3Tfk/jwOOMpPt4J7CGbNxxKFi+/qiCxLNCSFXF2+UMwJ9UbKZA+vIMEw9 Ecgkpy53KtZ9Xds0o6IGyJjHR6UQV848c/Miawikz0Cc11b+aq3gvWMhuhGXVnIp zjyzJxgkk9FYdL8KtTYASIOo7nsieUdPAgMBAAGjXTBbMAwGA1UdEwEB/wQCMAAw CwYDVR0PBAQDAgWgMB0GA1UdDgQWBBQX5KXbc5jNpdtKb/6mGOGVa4L6VTAfBgNV HSMEGDAWgBQCiVq1mATQGX/9xPxG9l0soukgFzANBgkqhkiG9w0BAQsFAAOCAQEA q47hqIOjO+005XUBiekSuHi0QA0B79p4tKbCSFtXA0kmmW22Cg4HTZWR9oIzB3my DukHHcpn/53xeTZXVbDiptorGX3jpaBjDlD/ELl7YFYNNlenwkXa1IRlSlbmYhx9 O2PsRnz73R6ebybqN4fpNUHy0cHqe8KNkhRI5YPhSWfIo5dbVyiD9jsOy5vhT+am Bt5Adk+gMFm3hok3aO500exAIscteflwDWyb1w6jShyoRX1YahJI5QU+MICIL+5k 3rKO4FK2Vo6wI6dk8ReMGRrZCBzfUxwCBsS+kQ5jwYym4XOw/62oealELP/Gm/Pp bWhwbV/AcUIgSZC76ZSoJQ== -----END CERTIFICATE----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/server-key.pem000066400000000000000000000032501504567405200246460ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzjC1+i1/DoFk1 wNOQENIC15+kL/FKk5r1JHCB6h3pOgKre1eAwVICOQ2EgD76OarvQNK4ECydOacY 09uCjR+BIWn65fDpC8jGlfZmgGr3IiLoIyKkoqXv8ZUxU/lTcFs4TKjCioXiYXwc CnVW7mG95I37IDtul/bUh+aySE7T2b+6tdwUKsQGHF1PULH3Tfk/jwOOMpPt4J7C GbNxxKFi+/qiCxLNCSFXF2+UMwJ9UbKZA+vIMEw9Ecgkpy53KtZ9Xds0o6IGyJjH R6UQV848c/Miawikz0Cc11b+aq3gvWMhuhGXVnIpzjyzJxgkk9FYdL8KtTYASIOo 7nsieUdPAgMBAAECggEAEPLiFoh8lUBtO2xE7FwSHw+Qs9SMv//4CD0U28aoZSxD NUHS7EYTgj81ffUHPOK1tpkVayentn3LPsY8+fFtcGihkvwixjUFEm30kQ99SW/x AJ3Udtsds+1HqpzlM9Gu4r0lzxt5cPnH1/PKyNZ+5oiNOI/93D4/ICfmCJ1Xx5ql W/4QzYzo+D2L5fDHBB9RrHWmb4eKUsDvXFqUlz50dbKtwdIT307j+27HjpaX0UKx Jf0LDn9KsygzHgsSQwJ2pvZrrpTS4pmSlz397+WINgYQd+XkcasHbdsJrcSXAOg3 J6eyE5kEEGYMHoZSpYQ2Jhpb7QB/RiR1vhJuSQ2IqQKBgQDr2Qd/VBzkj+ppNgp8 KTMHLdZDexuTUbfm0lqmbHHbaYXgc1iYAKLvGDHvrxEZsnDUSyf+lAeJuaVY5KdV T4ogCrI6Zdm4gux/8LsqyKMd3JH6DnaHYDmChQudXdkbNgdgsoo3G59zmbDMvfrL mxmL49lkQ8jSE/3owO3KaXgd2QKBgQDC46EL2L/eteuZ3ocIzttffPyuO2/PlSz/ I+ij9pG+9EMqMlhy1mG3FYvunSUex4FoUjIz8SETWiaX/+n8nU3sqMsCXK6U78ga Nhe8bmshfFICwzT7cOzYJY5gkFtqV3xie9BrrM8SM5VwmJuAdJYdDiC+Qub/2/+g 57SzeaNNZwKBgGy7I8+58ZAWIVXcCj1vqQzYPv3hVbc3Z3dM52nueRdUsNnnk6KQ OI3OM8dyiIm2UHovJAMkL814/xfaYqLcBqv7AmwV5KhCA9KAI2n4EeuEcvA7lr2W ySy5Nb+ZMqxu3jvgVARQAdUDuBTMSUFxAfgSVXj6Hy1q9hZGS9qTgUMRAoGBAIqE J064O4cbXdz7IJbOD3WK7D0Z2Zp8uIKPDyaadXR3P9WZ+uuEG+d41QA/iMabngp7 gVsRoySSCqQ2LCRz2ZK/VarUHPGWi261y6EOCe6+4bs860dbN7tY1h0j/RVUIQAO aFBffr29FBX3IW7nblowVG1mN7DauJGwneqCJeM5AoGBAOc6vMhl355zR/5VK2Sb PtKrrbpHmJSeqW7wLBNWXNPkFInrf5G8m6oMQISVjqJ+dlP/AKZE2dOqt6+XyK7T QWhln1l4+Gbx+o9ig6/nrisSYPIoZXDUht0+GYbCEBK1p/R+8k4CM1FrVLIaufQj 1wx+hdkof1ICK9gnjBDc/DwL -----END PRIVATE KEY----- valkey-io-valkey-py-350fd2d/dockers/stunnel/keys/server-req.pem000066400000000000000000000017001504567405200246430ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICjDCCAXQCAQAwGzEZMBcGA1UEAwwQdmFsa2V5LXB5LXNlcnZlcjCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBALOMLX6LX8OgWTXA05AQ0gLXn6Qv8UqT mvUkcIHqHek6Aqt7V4DBUgI5DYSAPvo5qu9A0rgQLJ05pxjT24KNH4Ehafrl8OkL yMaV9maAavciIugjIqSipe/xlTFT+VNwWzhMqMKKheJhfBwKdVbuYb3kjfsgO26X 9tSH5rJITtPZv7q13BQqxAYcXU9QsfdN+T+PA44yk+3gnsIZs3HEoWL7+qILEs0J IVcXb5QzAn1RspkD68gwTD0RyCSnLncq1n1d2zSjogbImMdHpRBXzjxz8yJrCKTP QJzXVv5qreC9YyG6EZdWcinOPLMnGCST0Vh0vwq1NgBIg6jueyJ5R08CAwEAAaAs MCoGCSqGSIb3DQEJDjEdMBswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBaAwDQYJ KoZIhvcNAQELBQADggEBAEJabnLktbb21WSoTTaC2mpLwSIGLGFXVQEFepdwmoKm RKqhqEz/Tw9LwC2EDPndjV1pzQf/yf1AYosMG/OHC+TyqsDyNdBXi+qbGLJrUVEc leE9swf52prrK6fauxZgsDJPRtHqzu40yrNkNz+wPRm7OvDiePdbqW5LhzepdBJV wqK6AEXfIJDn9zkMhVxYYCff9QaqOBUYiTaptAAH2K6wGzL7CXp7SnDnDLMWAheD JIoRYZVXCV/3u/p67pUcndKZA2CvuI+fslmMVaoRMzcaXZ2dxXbr9OrJAFJNeqCg nbOlMZvgLjvAiOqMIoYtGasJnlolb0Fg2yurPIqaisM= -----END CERTIFICATE REQUEST----- valkey-io-valkey-py-350fd2d/dockers/stunnel/openssl.cnf000066400000000000000000000005021504567405200232440ustar00rootroot00000000000000[ req ] distinguished_name = req_distinguished_name x509_extensions = v3_ca [ req_distinguished_name ] commonName = valkey.io commonName_max = 64 [ v3_ca ] basicConstraints = critical, CA:TRUE keyUsage = keyCertSign, cRLSign [ v3_req ] basicConstraints = critical, CA:FALSE keyUsage = digitalSignature, keyEncipherment valkey-io-valkey-py-350fd2d/docs/000077500000000000000000000000001504567405200167025ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/docs/Makefile000066400000000000000000000127101504567405200203430ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/valkey-py.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/valkey-py.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/valkey-py" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/valkey-py" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." valkey-io-valkey-py-350fd2d/docs/_static/000077500000000000000000000000001504567405200203305ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/docs/_static/.keep000066400000000000000000000000001504567405200212430ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/docs/_static/Valkey-logo.svg000066400000000000000000000025231504567405200232440ustar00rootroot00000000000000 valkey-io-valkey-py-350fd2d/docs/_templates/000077500000000000000000000000001504567405200210375ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/docs/_templates/.keep000066400000000000000000000000001504567405200217520ustar00rootroot00000000000000valkey-io-valkey-py-350fd2d/docs/advanced_features.rst000066400000000000000000000446661504567405200231170ustar00rootroot00000000000000Advanced Features ================= A note about threading ---------------------- Valkey client instances can safely be shared between threads. Internally, connection instances are only retrieved from the connection pool during command execution, and returned to the pool directly after. Command execution never modifies state on the client instance. However, there is one caveat: the Valkey SELECT command. The SELECT command allows you to switch the database currently in use by the connection. That database remains selected until another is selected or until the connection is closed. This creates an issue in that connections could be returned to the pool that are connected to a different database. As a result, valkey-py does not implement the SELECT command on client instances. If you use multiple Valkey databases within the same application, you should create a separate client instance (and possibly a separate connection pool) for each database. It is not safe to pass PubSub or Pipeline objects between threads. Pipelines --------- Default pipelines ~~~~~~~~~~~~~~~~~ Pipelines are a subclass of the base Valkey class that provide support for buffering multiple commands to the server in a single request. They can be used to dramatically increase the performance of groups of commands by reducing the number of back-and-forth TCP packets between the client and server. Pipelines are quite simple to use: .. code:: python >>> r = valkey.Valkey(...) >>> r.set('bing', 'baz') >>> # Use the pipeline() method to create a pipeline instance >>> pipe = r.pipeline() >>> # The following SET commands are buffered >>> pipe.set('foo', 'bar') >>> pipe.get('bing') >>> # the EXECUTE call sends all buffered commands to the server, returning >>> # a list of responses, one for each command. >>> pipe.execute() [True, b'baz'] For ease of use, all commands being buffered into the pipeline return the pipeline object itself. Therefore calls can be chained like: .. code:: python >>> pipe.set('foo', 'bar').sadd('faz', 'baz').incr('auto_number').execute() [True, True, 6] In addition, pipelines can also ensure the buffered commands are executed atomically as a group. This happens by default. If you want to disable the atomic nature of a pipeline but still want to buffer commands, you can turn off transactions. .. code:: python >>> pipe = r.pipeline(transaction=False) A common issue occurs when requiring atomic transactions but needing to retrieve values in Valkey prior for use within the transaction. For instance, let's assume that the INCR command didn't exist and we need to build an atomic version of INCR in Python. The completely naive implementation could GET the value, increment it in Python, and SET the new value back. However, this is not atomic because multiple clients could be doing this at the same time, each getting the same value from GET. Enter the WATCH command. WATCH provides the ability to monitor one or more keys prior to starting a transaction. If any of those keys change prior the execution of that transaction, the entire transaction will be canceled and a WatchError will be raised. To implement our own client-side INCR command, we could do something like this: .. code:: python >>> with r.pipeline() as pipe: ... while True: ... try: ... # put a WATCH on the key that holds our sequence value ... pipe.watch('OUR-SEQUENCE-KEY') ... # after WATCHing, the pipeline is put into immediate execution ... # mode until we tell it to start buffering commands again. ... # this allows us to get the current value of our sequence ... current_value = pipe.get('OUR-SEQUENCE-KEY') ... next_value = int(current_value) + 1 ... # now we can put the pipeline back into buffered mode with MULTI ... pipe.multi() ... pipe.set('OUR-SEQUENCE-KEY', next_value) ... # and finally, execute the pipeline (the set command) ... pipe.execute() ... # if a WatchError wasn't raised during execution, everything ... # we just did happened atomically. ... break ... except WatchError: ... # another client must have changed 'OUR-SEQUENCE-KEY' between ... # the time we started WATCHing it and the pipeline's execution. ... # our best bet is to just retry. ... continue Note that, because the Pipeline must bind to a single connection for the duration of a WATCH, care must be taken to ensure that the connection is returned to the connection pool by calling the reset() method. If the Pipeline is used as a context manager (as in the example above) reset() will be called automatically. Of course you can do this the manual way by explicitly calling reset(): .. code:: python >>> pipe = r.pipeline() >>> while True: ... try: ... pipe.watch('OUR-SEQUENCE-KEY') ... ... ... pipe.execute() ... break ... except WatchError: ... continue ... finally: ... pipe.reset() A convenience method named "transaction" exists for handling all the boilerplate of handling and retrying watch errors. It takes a callable that should expect a single parameter, a pipeline object, and any number of keys to be WATCHed. Our client-side INCR command above can be written like this, which is much easier to read: .. code:: python >>> def client_side_incr(pipe): ... current_value = pipe.get('OUR-SEQUENCE-KEY') ... next_value = int(current_value) + 1 ... pipe.multi() ... pipe.set('OUR-SEQUENCE-KEY', next_value) >>> >>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY') [True] Be sure to call pipe.multi() in the callable passed to Valkey.transaction prior to any write commands. Pipelines in clusters ~~~~~~~~~~~~~~~~~~~~~ ClusterPipeline is a subclass of ValkeyCluster that provides support for Valkey pipelines in cluster mode. When calling the execute() command, all the commands are grouped by the node on which they will be executed, and are then executed by the respective nodes in parallel. The pipeline instance will wait for all the nodes to respond before returning the result to the caller. Command responses are returned as a list sorted in the same order in which they were sent. Pipelines can be used to dramatically increase the throughput of Valkey Cluster by significantly reducing the number of network round trips between the client and the server. .. code:: python >>> with rc.pipeline() as pipe: ... pipe.set('foo', 'value1') ... pipe.set('bar', 'value2') ... pipe.get('foo') ... pipe.get('bar') ... print(pipe.execute()) [True, True, b'value1', b'value2'] ... pipe.set('foo1', 'bar1').get('foo1').execute() [True, b'bar1'] Please note: - ValkeyCluster pipelines currently only support key-based commands. - The pipeline gets its โ€˜read_from_replicasโ€™ value from the clusterโ€™s parameter. Thus, if read from replications is enabled in the cluster instance, the pipeline will also direct read commands to replicas. - The โ€˜transactionโ€™ option is NOT supported in cluster-mode. In non-cluster mode, the โ€˜transactionโ€™ option is available when executing pipelines. This wraps the pipeline commands with MULTI/EXEC commands, and effectively turns the pipeline commands into a single transaction block. This means that all commands are executed sequentially without any interruptions from other clients. However, in cluster-mode this is not possible, because commands are partitioned according to their respective destination nodes. This means that we can not turn the pipeline commands into one transaction block, because in most cases they are split up into several smaller pipelines. Publish / Subscribe ------------------- valkey-py includes a PubSub object that subscribes to channels and listens for new messages. Creating a PubSub object is easy. .. code:: python >>> r = valkey.Valkey(...) >>> p = r.pubsub() Once a PubSub instance is created, channels and patterns can be subscribed to. .. code:: python >>> p.subscribe('my-first-channel', 'my-second-channel', ...) >>> p.psubscribe('my-*', ...) The PubSub instance is now subscribed to those channels/patterns. The subscription confirmations can be seen by reading messages from the PubSub instance. .. code:: python >>> p.get_message() {'pattern': None, 'type': 'subscribe', 'channel': b'my-second-channel', 'data': 1} >>> p.get_message() {'pattern': None, 'type': 'subscribe', 'channel': b'my-first-channel', 'data': 2} >>> p.get_message() {'pattern': None, 'type': 'psubscribe', 'channel': b'my-*', 'data': 3} Every message read from a PubSub instance will be a dictionary with the following keys. - **type**: One of the following: 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe', 'message', 'pmessage' - **channel**: The channel [un]subscribed to or the channel a message was published to - **pattern**: The pattern that matched a published message's channel. Will be None in all cases except for 'pmessage' types. - **data**: The message data. With [un]subscribe messages, this value will be the number of channels and patterns the connection is currently subscribed to. With [p]message messages, this value will be the actual published message. Let's send a message now. .. code:: python # the publish method returns the number matching channel and pattern # subscriptions. 'my-first-channel' matches both the 'my-first-channel' # subscription and the 'my-*' pattern subscription, so this message will # be delivered to 2 channels/patterns >>> r.publish('my-first-channel', 'some data') 2 >>> p.get_message() {'channel': b'my-first-channel', 'data': b'some data', 'pattern': None, 'type': 'message'} >>> p.get_message() {'channel': b'my-first-channel', 'data': b'some data', 'pattern': b'my-*', 'type': 'pmessage'} Unsubscribing works just like subscribing. If no arguments are passed to [p]unsubscribe, all channels or patterns will be unsubscribed from. .. code:: python >>> p.unsubscribe() >>> p.punsubscribe('my-*') >>> p.get_message() {'channel': b'my-second-channel', 'data': 2, 'pattern': None, 'type': 'unsubscribe'} >>> p.get_message() {'channel': b'my-first-channel', 'data': 1, 'pattern': None, 'type': 'unsubscribe'} >>> p.get_message() {'channel': b'my-*', 'data': 0, 'pattern': None, 'type': 'punsubscribe'} valkey-py also allows you to register callback functions to handle published messages. Message handlers take a single argument, the message, which is a dictionary just like the examples above. To subscribe to a channel or pattern with a message handler, pass the channel or pattern name as a keyword argument with its value being the callback function. When a message is read on a channel or pattern with a message handler, the message dictionary is created and passed to the message handler. In this case, a None value is returned from get_message() since the message was already handled. .. code:: python >>> def my_handler(message): ... print('MY HANDLER: ', message['data']) >>> p.subscribe(**{'my-channel': my_handler}) # read the subscribe confirmation message >>> p.get_message() {'pattern': None, 'type': 'subscribe', 'channel': b'my-channel', 'data': 1} >>> r.publish('my-channel', 'awesome data') 1 # for the message handler to work, we need tell the instance to read data. # this can be done in several ways (read more below). we'll just use # the familiar get_message() function for now >>> message = p.get_message() MY HANDLER: awesome data # note here that the my_handler callback printed the string above. # `message` is None because the message was handled by our handler. >>> print(message) None If your application is not interested in the (sometimes noisy) subscribe/unsubscribe confirmation messages, you can ignore them by passing ignore_subscribe_messages=True to r.pubsub(). This will cause all subscribe/unsubscribe messages to be read, but they won't bubble up to your application. .. code:: python >>> p = r.pubsub(ignore_subscribe_messages=True) >>> p.subscribe('my-channel') >>> p.get_message() # hides the subscribe message and returns None >>> r.publish('my-channel', 'my data') 1 >>> p.get_message() {'channel': b'my-channel', 'data': b'my data', 'pattern': None, 'type': 'message'} There are three different strategies for reading messages. The examples above have been using pubsub.get_message(). Behind the scenes, get_message() uses the system's 'select' module to quickly poll the connection's socket. If there's data available to be read, get_message() will read it, format the message and return it or pass it to a message handler. If there's no data to be read, get_message() will immediately return None. This makes it trivial to integrate into an existing event loop inside your application. .. code:: python >>> while True: >>> message = p.get_message() >>> if message: >>> # do something with the message >>> time.sleep(0.001) # be nice to the system :) listen() is a generator that blocks until a message is available. If your application doesn't need to do anything else but receive and act on messages received from valkey, listen() is an easy way to get up an running. .. code:: python >>> for message in p.listen(): ... # do something with the message The third option runs an event loop in a separate thread. pubsub.run_in_thread() creates a new thread and starts the event loop. The thread object is returned to the caller of run_in_thread(). The caller can use the thread.stop() method to shut down the event loop and thread. Behind the scenes, this is simply a wrapper around get_message() that runs in a separate thread, essentially creating a tiny non-blocking event loop for you. run_in_thread() takes an optional sleep_time argument. If specified, the event loop will call time.sleep() with the value in each iteration of the loop. Note: Since we're running in a separate thread, there's no way to handle messages that aren't automatically handled with registered message handlers. Therefore, valkey-py prevents you from calling run_in_thread() if you're subscribed to patterns or channels that don't have message handlers attached. .. code:: python >>> p.subscribe(**{'my-channel': my_handler}) >>> thread = p.run_in_thread(sleep_time=0.001) # the event loop is now running in the background processing messages # when it's time to shut it down... >>> thread.stop() run_in_thread also supports an optional exception handler, which lets you catch exceptions that occur within the worker thread and handle them appropriately. The exception handler will take as arguments the exception itself, the pubsub object, and the worker thread returned by run_in_thread. .. code:: python >>> p.subscribe(**{'my-channel': my_handler}) >>> def exception_handler(ex, pubsub, thread): >>> print(ex) >>> thread.stop() >>> thread.join(timeout=1.0) >>> pubsub.close() >>> thread = p.run_in_thread(exception_handler=exception_handler) A PubSub object adheres to the same encoding semantics as the client instance it was created from. Any channel or pattern that's unicode will be encoded using the charset specified on the client before being sent to Valkey. If the client's decode_responses flag is set the False (the default), the 'channel', 'pattern' and 'data' values in message dictionaries will be byte strings (str on Python 2, bytes on Python 3). If the client's decode_responses is True, then the 'channel', 'pattern' and 'data' values will be automatically decoded to unicode strings using the client's charset. PubSub objects remember what channels and patterns they are subscribed to. In the event of a disconnection such as a network error or timeout, the PubSub object will re-subscribe to all prior channels and patterns when reconnecting. Messages that were published while the client was disconnected cannot be delivered. When you're finished with a PubSub object, call its .close() method to shutdown the connection. .. code:: python >>> p = r.pubsub() >>> ... >>> p.close() The PUBSUB set of subcommands CHANNELS, NUMSUB and NUMPAT are also supported: .. code:: python >>> r.pubsub_channels() [b'foo', b'bar'] >>> r.pubsub_numsub('foo', 'bar') [(b'foo', 9001), (b'bar', 42)] >>> r.pubsub_numsub('baz') [(b'baz', 0)] >>> r.pubsub_numpat() 1204 Sharded pubsub ~~~~~~~~~~~~~~ `Sharded pubsub `_ is a feature that helps scale the usage of pub/sub in cluster mode, by having the cluster shard messages to nodes that own a slot for a shard channel. Here, the cluster ensures the published shard messages are forwarded to the appropriate nodes. Clients subscribe to a channel by connecting to either the master responsible for the slot, or any of its replicas. This makes use of the `SSUBSCRIBE `_ and `SPUBLISH `_ commands within Valkey. The following, is a simplified example: .. code:: python >>> from valkey.cluster import ValkeyCluster, ClusterNode >>> r = ValkeyCluster(startup_nodes=[ClusterNode('localhost', 6379), ClusterNode('localhost', 6380)]) >>> p = r.pubsub() >>> p.ssubscribe('foo') >>> # assume someone sends a message along the channel via a publish >>> message = p.get_sharded_message() Similarly, the same process can be used to acquire sharded pubsub messages, that have already been sent to a specific node, by passing the node to get_sharded_message: .. code:: python >>> from valkey.cluster import ValkeyCluster, ClusterNode >>> first_node = ClusterNode['localhost', 6379] >>> second_node = ClusterNode['localhost', 6380] >>> r = ValkeyCluster(startup_nodes=[first_node, second_node]) >>> p = r.pubsub() >>> p.ssubscribe('foo') >>> # assume someone sends a message along the channel via a publish >>> message = p.get_sharded_message(target_node=second_node) Monitor ~~~~~~~ valkey-py includes a Monitor object that streams every command processed by the Valkey server. Use listen() on the Monitor object to block until a command is received. .. code:: python >>> r = valkey.Valkey(...) >>> with r.monitor() as m: >>> for command in m.listen(): >>> print(command) valkey-io-valkey-py-350fd2d/docs/backoff.rst000066400000000000000000000001301504567405200210210ustar00rootroot00000000000000.. _backoff-label: Backoff ############# .. automodule:: valkey.backoff :members: valkey-io-valkey-py-350fd2d/docs/clustering.rst000066400000000000000000000250321504567405200216150ustar00rootroot00000000000000Clustering ========== valkey-py supports cluster mode and provides a client for `Valkey Cluster `__. The cluster client is based on Grokzenโ€™s `redis-py-cluster `__, has added bug fixes, and now supersedes that library. Support for these changes is thanks to his contributions. To learn more about Valkey Cluster, see `Valkey Cluster specifications `__. `Creating clusters <#creating-clusters>`__ \| `Specifying Target Nodes <#specifying-target-nodes>`__ \| `Multi-key Commands <#multi-key-commands>`__ \| `Known PubSub Limitations <#known-pubsub-limitations>`__ Creating clusters ----------------- Connecting valkey-py to a Valkey Cluster instance(s) requires at a minimum a single node for cluster discovery. There are multiple ways in which a cluster instance can be created: - Using โ€˜hostโ€™ and โ€˜portโ€™ arguments: .. code:: python >>> from valkey.cluster import ValkeyCluster as Valkey >>> rc = Valkey(host='localhost', port=6379) >>> print(rc.get_nodes()) [[host=127.0.0.1,port=6379,name=127.0.0.1:6379,server_type=primary,valkey_connection=Valkey>>], [host=127.0.0.1,port=6378,name=127.0.0.1:6378,server_type=primary,valkey_connection=Valkey>>], [host=127.0.0.1,port=6377,name=127.0.0.1:6377,server_type=replica,valkey_connection=Valkey>>]] - Using the Valkey URL specification: .. code:: python >>> from valkey.cluster import ValkeyCluster as Valkey >>> rc = Valkey.from_url("valkey://localhost:6379/0") - Directly, via the ClusterNode class: .. code:: python >>> from valkey.cluster import ValkeyCluster as Valkey >>> from valkey.cluster import ClusterNode >>> nodes = [ClusterNode('localhost', 6379), ClusterNode('localhost', 6378)] >>> rc = Valkey(startup_nodes=nodes) When a ValkeyCluster instance is being created it first attempts to establish a connection to one of the provided startup nodes. If none of the startup nodes are reachable, a โ€˜ValkeyClusterExceptionโ€™ will be thrown. After a connection to the one of the clusterโ€™s nodes is established, the ValkeyCluster instance will be initialized with 3 caches: a slots cache which maps each of the 16384 slots to the node/s handling them, a nodes cache that contains ClusterNode objects (name, host, port, valkey connection) for all of the clusterโ€™s nodes, and a commands cache contains all the server supported commands that were retrieved using the Valkey โ€˜COMMANDโ€™ output. See *ValkeyCluster specific options* below for more. ValkeyCluster instance can be directly used to execute Valkey commands. When a command is being executed through the cluster instance, the target node(s) will be internally determined. When using a key-based command, the target node will be the node that holds the keyโ€™s slot. Cluster management commands and other commands that are not key-based have a parameter called โ€˜target_nodesโ€™ where you can specify which nodes to execute the command on. In the absence of target_nodes, the command will be executed on the default cluster node. As part of cluster instance initialization, the clusterโ€™s default node is randomly selected from the clusterโ€™s primaries, and will be updated upon reinitialization. Using r.get_default_node(), you can get the clusterโ€™s default node, or you can change it using the โ€˜set_default_nodeโ€™ method. The โ€˜target_nodesโ€™ parameter is explained in the following section, โ€˜Specifying Target Nodesโ€™. .. code:: python >>> # target-nodes: the node that holds 'foo1's key slot >>> rc.set('foo1', 'bar1') >>> # target-nodes: the node that holds 'foo2's key slot >>> rc.set('foo2', 'bar2') >>> # target-nodes: the node that holds 'foo1's key slot >>> print(rc.get('foo1')) b'bar' >>> # target-node: default-node >>> print(rc.keys()) [b'foo1'] >>> # target-node: default-node >>> rc.ping() Specifying Target Nodes ----------------------- As mentioned above, all non key-based ValkeyCluster commands accept the kwarg parameter โ€˜target_nodesโ€™ that specifies the node/nodes that the command should be executed on. The best practice is to specify target nodes using ValkeyCluster classโ€™s node flags: PRIMARIES, REPLICAS, ALL_NODES, RANDOM. When a nodes flag is passed along with a command, it will be internally resolved to the relevant node/s. If the nodes topology of the cluster changes during the execution of a command, the client will be able to resolve the nodes flag again with the new topology and attempt to retry executing the command. .. code:: python >>> from valkey.cluster import ValkeyCluster as Valkey >>> # run cluster-meet command on all of the cluster's nodes >>> rc.cluster_meet('127.0.0.1', 6379, target_nodes=Valkey.ALL_NODES) >>> # ping all replicas >>> rc.ping(target_nodes=Valkey.REPLICAS) >>> # ping a random node >>> rc.ping(target_nodes=Valkey.RANDOM) >>> # get the keys from all cluster nodes >>> rc.keys(target_nodes=Valkey.ALL_NODES) [b'foo1', b'foo2'] >>> # execute bgsave in all primaries >>> rc.bgsave(Valkey.PRIMARIES) You could also pass ClusterNodes directly if you want to execute a command on a specific node / node group that isnโ€™t addressed by the nodes flag. However, if the command execution fails due to cluster topology changes, a retry attempt will not be made, since the passed target node/s may no longer be valid, and the relevant cluster or connection error will be returned. .. code:: python >>> node = rc.get_node('localhost', 6379) >>> # Get the keys only for that specific node >>> rc.keys(target_nodes=node) >>> # get Valkey info from a subset of primaries >>> subset_primaries = [node for node in rc.get_primaries() if node.port > 6378] >>> rc.info(target_nodes=subset_primaries) In addition, the ValkeyCluster instance can query the Valkey instance of a specific node and execute commands on that node directly. The Valkey client, however, does not handle cluster failures and retries. .. code:: python >>> cluster_node = rc.get_node(host='localhost', port=6379) >>> print(cluster_node) [host=127.0.0.1,port=6379,name=127.0.0.1:6379,server_type=primary,valkey_connection=Valkey>>] >>> r = cluster_node.valkey_connection >>> r.client_list() [{'id': '276', 'addr': '127.0.0.1:64108', 'fd': '16', 'name': '', 'age': '0', 'idle': '0', 'flags': 'N', 'db': '0', 'sub': '0', 'psub': '0', 'multi': '-1', 'qbuf': '26', 'qbuf-free': '32742', 'argv-mem': '10', 'obl': '0', 'oll': '0', 'omem': '0', 'tot-mem': '54298', 'events': 'r', 'cmd': 'client', 'user': 'default'}] >>> # Get the keys only for that specific node >>> r.keys() [b'foo1'] Multi-key Commands ------------------ Valkey supports multi-key commands in Cluster Mode, such as Set type unions or intersections, mset and mget, as long as the keys all hash to the same slot. By using ValkeyCluster client, you can use the known functions (e.g.ย mget, mset) to perform an atomic multi-key operation. However, you must ensure all keys are mapped to the same slot, otherwise a ValkeyClusterException will be thrown. Valkey Cluster implements a concept called hash tags that can be used in order to force certain keys to be stored in the same hash slot, see `Keys hash tag `__. You can also use nonatomic for some of the multikey operations, and pass keys that arenโ€™t mapped to the same slot. The client will then map the keys to the relevant slots, sending the commands to the slotsโ€™ node owners. Non-atomic operations batch the keys according to their hash value, and then each batch is sent separately to the slotโ€™s owner. .. code:: python # Atomic operations can be used when all keys are mapped to the same slot >>> rc.mset({'{foo}1': 'bar1', '{foo}2': 'bar2'}) >>> rc.mget('{foo}1', '{foo}2') [b'bar1', b'bar2'] # Non-atomic multi-key operations splits the keys into different slots >>> rc.mset_nonatomic({'foo': 'value1', 'bar': 'value2', 'zzz': 'value3') >>> rc.mget_nonatomic('foo', 'bar', 'zzz') [b'value1', b'value2', b'value3'] **Cluster PubSub:** When a ClusterPubSub instance is created without specifying a node, a single node will be transparently chosen for the pubsub connection on the first command execution. The node will be determined by: 1. Hashing the channel name in the request to find its keyslot 2. Selecting a node that handles the keyslot: If read_from_replicas is set to true, a replica can be selected. Known PubSub Limitations ------------------------ Pattern subscribe and publish do not currently work properly due to key slots. If we hash a pattern like fo\* we will receive a keyslot for that string but there are endless possibilities for channel names based on this pattern - unknowable in advance. This feature is not disabled but the commands are not currently recommended for use. .. code:: python >>> p1 = rc.pubsub() # p1 connection will be set to the node that holds 'foo' keyslot >>> p1.subscribe('foo') # p2 connection will be set to node 'localhost:6379' >>> p2 = rc.pubsub(rc.get_node('localhost', 6379)) **Read Only Mode** By default, Valkey Cluster always returns MOVE redirection response on accessing a replica node. You can overcome this limitation and scale read commands by triggering READONLY mode. To enable READONLY mode pass read_from_replicas=True to ValkeyCluster constructor. When set to true, read commands will be assigned between the primary and its replications in a Round-Robin manner. READONLY mode can be set at runtime by calling the readonly() method with target_nodes=โ€˜replicasโ€™, and read-write access can be restored by calling the readwrite() method. .. code:: python >>> from cluster import ValkeyCluster as Valkey # Use 'debug' log level to print the node that the command is executed on >>> rc_readonly = Valkey(startup_nodes=startup_nodes, ... read_from_replicas=True) >>> rc_readonly.set('{foo}1', 'bar1') >>> for i in range(0, 4): ... # Assigns read command to the slot's hosts in a Round-Robin manner ... rc_readonly.get('{foo}1') # set command would be directed only to the slot's primary node >>> rc_readonly.set('{foo}2', 'bar2') # reset READONLY flag >>> rc_readonly.readwrite(target_nodes='replicas') # now the get command would be directed only to the slot's primary node >>> rc_readonly.get('{foo}1') valkey-io-valkey-py-350fd2d/docs/commands.rst000066400000000000000000000017211504567405200212360ustar00rootroot00000000000000Valkey Commands ############## Core Commands ************* The following functions can be used to replicate their equivalent `Valkey command `_. Generally they can be used as functions on your valkey connection. For the simplest example, see below: Getting and settings data in valkey:: import valkey r = valkey.Valkey(decode_responses=True) r.set('mykey', 'thevalueofmykey') r.get('mykey') .. autoclass:: valkey.commands.core.CoreCommands :inherited-members: Sentinel Commands ***************** .. autoclass:: valkey.commands.sentinel.SentinelCommands :inherited-members: Valkey Cluster Commands ********************** The following `Valkey commands `_ are available within a `Valkey Cluster `_. Generally they can be used as functions on your valkey connection. .. autoclass:: valkey.commands.cluster.ValkeyClusterCommands :inherited-members: valkey-io-valkey-py-350fd2d/docs/conf.py000066400000000000000000000232111504567405200202000ustar00rootroot00000000000000# valkey-py documentation build configuration file, created by # sphinx-quickstart on Fri Feb 8 00:47:08 2013. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import datetime import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) sys.path.append(os.path.abspath(os.path.pardir)) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "nbsphinx", "sphinx_gallery.load_style", "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.autosectionlabel", "sphinx.ext.napoleon", ] # Napoleon settings. We only accept Google-style docstrings. napoleon_google_docstring = True napoleon_numpy_docstring = False # AutosectionLabel settings. # Uses a :