From 005bd265f9f62dfee0d474ae2e4b935bd0ddb59f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 11:11:49 -0700 Subject: [PATCH 001/124] Follow quick start guide --- .github/workflows/doc-tests.yml | 17 +++++++++++++++++ aerospike_helpers/__init__.py | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/.github/workflows/doc-tests.yml b/.github/workflows/doc-tests.yml index e440389374..1bad3defd1 100644 --- a/.github/workflows/doc-tests.yml +++ b/.github/workflows/doc-tests.yml @@ -54,3 +54,20 @@ jobs: - name: Run builder run: sphinx-build -b ${{ matrix.builder-command }} working-directory: doc + + doctest: + runs-on: ubuntu-22.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: 3.12 + + - name: Run doctest + run: python3 aerospike_helpers/__init__.py diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index bad706b072..7f89244297 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -35,3 +35,7 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.__repr__() + +if __name__ == "__main__": + import doctest + doctest.testmod() From a7ac1177614fd7dad9705fa2a24e197c1f184e65 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 11:13:52 -0700 Subject: [PATCH 002/124] Prevent silent failing --- aerospike_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 7f89244297..042b8437c6 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -38,4 +38,4 @@ def __str__(self) -> str: if __name__ == "__main__": import doctest - doctest.testmod() + doctest.testmod(raise_on_error=True) From b9676ba3ddfeda2f9de47ba118aad5066c8d27d6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 11:17:18 -0700 Subject: [PATCH 003/124] Add doctest to test code examples in aerospike module rst docs. TODO - this may make cicd check redundant in doc tests. --- doc/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/conf.py b/doc/conf.py index c015fe4626..020a6c9426 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -37,6 +37,7 @@ def __getattr__(cls, name): extensions = [ "sphinx.ext.todo", "sphinx.ext.autodoc", + "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinxcontrib.spelling" From 9410da77a825eca4894b80e41e12cb0944ed9ef1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:38:25 -0700 Subject: [PATCH 004/124] Try running with doc/*.rst to see what files get inspected --- .github/workflows/doc-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc-tests.yml b/.github/workflows/doc-tests.yml index 1bad3defd1..9d69561e50 100644 --- a/.github/workflows/doc-tests.yml +++ b/.github/workflows/doc-tests.yml @@ -70,4 +70,4 @@ jobs: python-version: 3.12 - name: Run doctest - run: python3 aerospike_helpers/__init__.py + run: python3 -m doctest doc/*.rst From 054b8a1ba1d224cd6185c0fdda6fbf9c5aa0d1b0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:40:41 -0700 Subject: [PATCH 005/124] *.rst doesn't show any output. Try checking a specific file --- .github/workflows/doc-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc-tests.yml b/.github/workflows/doc-tests.yml index 9d69561e50..2e6d9ec526 100644 --- a/.github/workflows/doc-tests.yml +++ b/.github/workflows/doc-tests.yml @@ -70,4 +70,4 @@ jobs: python-version: 3.12 - name: Run doctest - run: python3 -m doctest doc/*.rst + run: python3 -m doctest doc/aerospike.rst From 7e1e1e6cf1a4dd1c5017bf7b9c235ab3546230f8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:44:44 -0700 Subject: [PATCH 006/124] need verbose mode to see number of tests run and whether they passed --- .github/workflows/doc-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc-tests.yml b/.github/workflows/doc-tests.yml index 2e6d9ec526..fe47896a7e 100644 --- a/.github/workflows/doc-tests.yml +++ b/.github/workflows/doc-tests.yml @@ -70,4 +70,4 @@ jobs: python-version: 3.12 - name: Run doctest - run: python3 -m doctest doc/aerospike.rst + run: python3 -m doctest -v doc/aerospike.rst From 2b0446c7ff9c3a1c8a40ae061032114a28024d57 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:52:07 -0700 Subject: [PATCH 007/124] Update code example to be doctest compatible. TODO - see how it appears in sphinx preview --- doc/aerospike.rst | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 3e4571a7c4..373a5b00f3 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -45,18 +45,12 @@ Client Simple example: - .. code-block:: python - - import aerospike - - # Configure the client to first connect to a cluster node at 127.0.0.1 - # The client will learn about the other nodes in the cluster from the seed node. - # Also sets a top level policy for read commands - config = { - 'hosts': [ ('127.0.0.1', 3000) ], - 'policies': {'read': {'total_timeout': 1000}}, - } - client = aerospike.client(config) + >>> import aerospike + >>> config = { + ... 'hosts': [ ('127.0.0.1', 3000) ], + ... 'policies': {'read': {'total_timeout': 1000}} + ... } + >>> client = aerospike.client(config) Connecting using TLS example: From aeee4d69e8d8dc2e4ce17c1f630c7ad2a25c3b87 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:59:53 -0700 Subject: [PATCH 008/124] Move doctest to smoke tests to use python client build in order to import it in the code examples --- .github/workflows/doc-tests.yml | 17 ----------------- .github/workflows/smoke-tests.yml | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/doc-tests.yml b/.github/workflows/doc-tests.yml index fe47896a7e..e440389374 100644 --- a/.github/workflows/doc-tests.yml +++ b/.github/workflows/doc-tests.yml @@ -54,20 +54,3 @@ jobs: - name: Run builder run: sphinx-build -b ${{ matrix.builder-command }} working-directory: doc - - doctest: - runs-on: ubuntu-22.04 - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 - with: - egress-policy: audit - - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: 3.12 - - - name: Run doctest - run: python3 -m doctest -v doc/aerospike.rst diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index e12244c9c0..b149837349 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -95,6 +95,31 @@ jobs: name: ${{ env.WHEEL_GH_ARTIFACT_NAME }} path: ./dist/*.whl + doctest: + needs: build + runs-on: ubuntu-22.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.LOWEST_SUPPORTED_PY_VERSION }} + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: wheel-${{ env.LOWEST_SUPPORTED_PY_VERSION }} + + - name: Install client + run: pip install *.whl + + - name: Run doctest + run: python3 -m doctest -v doc/aerospike.rst + build-and-run-tests-with-debug-interpreter: runs-on: ubuntu-22.04 steps: From cb8f30fdcd10b46ad0b896c3d96c264e32df5742 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:06:56 -0700 Subject: [PATCH 009/124] Reuse job that deploys server to run doctest examples --- .github/workflows/smoke-tests.yml | 32 +++++++------------------------ 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index b149837349..99ec20d011 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -95,31 +95,6 @@ jobs: name: ${{ env.WHEEL_GH_ARTIFACT_NAME }} path: ./dist/*.whl - doctest: - needs: build - runs-on: ubuntu-22.04 - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 - with: - egress-policy: audit - - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.LOWEST_SUPPORTED_PY_VERSION }} - - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: wheel-${{ env.LOWEST_SUPPORTED_PY_VERSION }} - - - name: Install client - run: pip install *.whl - - - name: Run doctest - run: python3 -m doctest -v doc/aerospike.rst - build-and-run-tests-with-debug-interpreter: runs-on: ubuntu-22.04 steps: @@ -313,6 +288,7 @@ jobs: - sanitizer - dont_validate_keys - lowest_supported_server_version + - doctest fail-fast: false runs-on: ubuntu-22.04 needs: build @@ -345,6 +321,7 @@ jobs: run: pip install *.whl - name: Install test dependencies + if: ${{ matrix.type != 'doctest' }} run: pip install -r test/requirements.txt # We can already detect leaks using valgrind @@ -365,7 +342,12 @@ jobs: run: crudini --existing=param --set config.conf input-validation validate_keys false working-directory: test + - if: ${{ matrix.type == 'doctest' }} + name: Run doctest + run: python3 -m doctest -v doc/aerospike.rst + - name: Run tests + if: ${{ matrix.type != 'doctest' }} # We need to disable capturing output or else we cannot see memory errors reported by # libasan during the test run # Here we also treat DeprecationWarnings as errors to make sure we're showing warnings as expected From ee3036685175bd60f17233718650d1fd70d37ebb Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:11:56 -0700 Subject: [PATCH 010/124] Move doctest to CE test to avoid needing to login as user --- .github/workflows/smoke-tests.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 99ec20d011..4386dfc341 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -288,7 +288,6 @@ jobs: - sanitizer - dont_validate_keys - lowest_supported_server_version - - doctest fail-fast: false runs-on: ubuntu-22.04 needs: build @@ -321,7 +320,6 @@ jobs: run: pip install *.whl - name: Install test dependencies - if: ${{ matrix.type != 'doctest' }} run: pip install -r test/requirements.txt # We can already detect leaks using valgrind @@ -342,12 +340,7 @@ jobs: run: crudini --existing=param --set config.conf input-validation validate_keys false working-directory: test - - if: ${{ matrix.type == 'doctest' }} - name: Run doctest - run: python3 -m doctest -v doc/aerospike.rst - - name: Run tests - if: ${{ matrix.type != 'doctest' }} # We need to disable capturing output or else we cannot see memory errors reported by # libasan during the test run # Here we also treat DeprecationWarnings as errors to make sure we're showing warnings as expected @@ -367,6 +360,14 @@ jobs: "3.13", "3.14" ] + type: [ + regular, + doc-test + ] + include: + - type: regular + - type: doc-test + py-version: '3.10' fail-fast: false steps: @@ -395,6 +396,7 @@ jobs: run: pip install *.whl - name: Install test dependencies + if: ${{ matrix.type != 'doctest' }} run: pip install -r test/requirements.txt - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 @@ -407,6 +409,7 @@ jobs: run: docker run -d --name aerospike -p 3000-3002:3000-3002 -e DEFAULT_TTL=2592000 ${{ env.REGISTRY_NAME }}/aerospike/aerospike-server:${{ env.SERVER_TAG }} - name: Create config.conf + if: ${{ matrix.type != 'doctest' }} run: cp config.conf.template config.conf working-directory: test @@ -415,9 +418,14 @@ jobs: container-name: aerospike - name: Run tests + if: ${{ matrix.type != 'doctest' }} run: python -m pytest ./new_tests -vv -W error::pytest.PytestUnraisableExceptionWarning working-directory: test + - if: ${{ matrix.type == 'doctest' }} + name: Run doctest + run: python3 -m doctest -v doc/aerospike.rst + test-ee: runs-on: ubuntu-22.04 needs: build From 5bede578c76094e35e638610be92ecf528ebcdf5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:19:07 -0700 Subject: [PATCH 011/124] Do this to avoid running doc test on every python version --- .github/workflows/smoke-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 4386dfc341..476d49fbe1 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -361,11 +361,9 @@ jobs: "3.14" ] type: [ - regular, - doc-test + regular ] include: - - type: regular - type: doc-test py-version: '3.10' fail-fast: false From 2fd18297b5ad371e2bf30cf12a0c8781f1b741a5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:22:33 -0700 Subject: [PATCH 012/124] fix workflow --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 476d49fbe1..03b8abb867 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -364,7 +364,7 @@ jobs: regular ] include: - - type: doc-test + - type: doctest py-version: '3.10' fail-fast: false From c8fe0075d6990ac1721304ad8a11955e901942ac Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:48:56 -0700 Subject: [PATCH 013/124] Add sphinx_copybutton extension to make copying doctest-formatted code examples easier --- doc/conf.py | 3 ++- doc/requirements.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 020a6c9426..8c45e93892 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -40,7 +40,8 @@ def __getattr__(cls, name): "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", - "sphinxcontrib.spelling" + "sphinxcontrib.spelling", + "sphinx_copybutton" ] napoleon_google_docstring = True intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} diff --git a/doc/requirements.txt b/doc/requirements.txt index 4662a72484..da5efab381 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -2,6 +2,7 @@ sphinx==9.1.0 sphinx-rtd-theme==3.1.0 sphinxcontrib-spelling==8.0.2 +sphinx-copybutton==0.5.2 certifi>=2023.7.22 # not directly required, pinned by Snyk to avoid a vulnerability jinja2>=3.1.3 # not directly required, pinned by Snyk to avoid a vulnerability pygments>=2.15.0 # not directly required, pinned by Snyk to avoid a vulnerability From 865ceae2a55bcc5a608508d4bacd48dd76bb623b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:54:55 -0700 Subject: [PATCH 014/124] Follow 'use and customize' guide for sphinx-copybutton to skip copying prompt chars --- doc/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 8c45e93892..31a7c201e6 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,6 +46,8 @@ def __getattr__(cls, name): napoleon_google_docstring = True intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} +copybutton_exclude = '.linenos, .gp' + # Add any paths that contain templates here, relative to this directory. templates_path = [] From 17aa74cf21bdc6eeeed8b61483bcff14dca65686 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:00:53 -0700 Subject: [PATCH 015/124] Add back comments --- doc/aerospike.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 373a5b00f3..8b644c35bb 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -45,7 +45,12 @@ Client Simple example: +.. TODO - Multiline comments maybe should be added to API descriptions + >>> import aerospike + >>> # Configure the client to first connect to a cluster node at 127.0.0.1 + >>> # The client will learn about the other nodes in the cluster from the seed node. + >>> # Also sets a top level policy for read commands >>> config = { ... 'hosts': [ ('127.0.0.1', 3000) ], ... 'policies': {'read': {'total_timeout': 1000}} From a15032a9fa13c91c94033a407994e02fa0175bb3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:09:05 -0700 Subject: [PATCH 016/124] Convert all code examples to be doctest compatible TODO - except for the TLS test which needs to be run with a different job --- doc/aerospike.rst | 153 ++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 86 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 8b644c35bb..9c509b4c32 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -64,8 +64,6 @@ Client import aerospike import sys - # NOTE: Use of TLS requires Aerospike Enterprise version >= 3.11 - # and client version 2.1.0 or greater tls_name = "some-server-tls-name" tls_ip = "127.0.0.1" tls_port = 4333 @@ -102,15 +100,12 @@ Geospatial :param dict geo_data: a :class:`dict` representing the geospatial data. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - .. code-block:: python - - import aerospike + >> import aerospike - # Create GeoJSON point using WGS84 coordinates. - latitude = 45.920278 - longitude = 63.342222 - loc = aerospike.geodata({'type': 'Point', - 'coordinates': [longitude, latitude]}) + >> # Create GeoJSON point using WGS84 coordinates. + >> latitude = 45.920278 + >> longitude = 63.342222 + >> loc = aerospike.geodata({'type': 'Point', 'coordinates': [longitude, latitude]}) .. versionadded:: 1.0.54 @@ -122,12 +117,10 @@ Geospatial :param dict geojson_str: a :class:`str` of raw GeoJSON. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - .. code-block:: python - - import aerospike + >> import aerospike - # Create GeoJSON point using WGS84 coordinates. - loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') + >> # Create GeoJSON point using WGS84 coordinates. + >> loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') .. versionadded:: 1.0.54 @@ -151,19 +144,17 @@ Types :return: a type representing a wildcard value. - .. code-block:: python + >> import aerospike + >> from aerospike_helpers.operations import list_operations as list_ops - import aerospike - from aerospike_helpers.operations import list_operations as list_ops + >> client = aerospike.client({'hosts': [('localhost', 3000)]}) + >> key = 'test', 'demo', 1 - client = aerospike.client({'hosts': [('localhost', 3000)]}) - key = 'test', 'demo', 1 - - # get all values of the form [1, ...] from a list of lists. - # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - # [1, 2, 3] and [1, 'a'] - operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] - _, _, bins = client.operate(key, operations) + >> # get all values of the form [1, ...] from a list of lists. + >> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + >> # [1, 2, 3] and [1, 'a'] + >> operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] + >> _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -175,19 +166,17 @@ Types :return: a type representing an infinite value. - .. code-block:: python + >> import aerospike + >> from aerospike_helpers.operations import list_operations as list_ops - import aerospike - from aerospike_helpers.operations import list_operations as list_ops - - client = aerospike.client({'hosts': [('localhost', 3000)]}) - key = 'test', 'demo', 1 + >> client = aerospike.client({'hosts': [('localhost', 3000)]}) + >> key = 'test', 'demo', 1 - # get all values of the form [1, ...] from a list of lists. - # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - # [1, 2, 3] and [1, 'a'] - operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] - _, _, bins = client.operate(key, operations) + >> # get all values of the form [1, ...] from a list of lists. + >> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + >> # [1, 2, 3] and [1, 'a'] + >> operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] + >> _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -213,12 +202,10 @@ Serialization .. seealso:: To use this function with :meth:`Client.put`, \ the argument to the serializer parameter should be :const:`aerospike.SERIALIZER_USER`. - .. code-block:: python + >> def my_serializer(val): + ... return json.dumps(val) - def my_serializer(val): - return json.dumps(val) - - aerospike.set_serializer(my_serializer) + >> aerospike.set_serializer(my_serializer) .. versionadded:: 1.0.39 @@ -335,13 +322,11 @@ Other :return: a RIPEMD-160 digest of the input tuple. :rtype: :class:`bytearray` - .. code-block:: python + >> import aerospike + >> import pprint - import aerospike - import pprint - - digest = aerospike.calc_digest("test", "demo", 1 ) - pp.pprint(digest) + >> digest = aerospike.calc_digest("test", "demo", 1 ) + >> pp.pprint(digest) .. _client_config: @@ -373,47 +358,43 @@ Only the `hosts` key is required; the rest of the keys are optional. Invalid client config example: - .. code-block:: python - - import aerospike - - config = { - "validate_keys": True, - "hosts": [ - ("127.0.0.1", 3000) - ], - # The correct key is "user", but "username" may be used by accident - "username": "user", - "password": "password" - } - # This call will raise a ParamError from aerospike.exception - # Exception message should be: - # "username" is an invalid client config dictionary key - client = aerospike.client(config) + >> import aerospike + + >> config = { + ... "validate_keys": True, + ... "hosts": [ + ... ("127.0.0.1", 3000) + ... ], + ... # The correct key is "user", but "username" may be used by accident + ... "username": "user", + ... "password": "password" + ... } + >> # This call will raise a ParamError from aerospike.exception + >> # Exception message should be: + >> # "username" is an invalid client config dictionary key + >> client = aerospike.client(config) Invalid policy example: - .. code-block:: python - - import aerospike - - config = { - "validate_keys": True, - "hosts": [ - ("127.0.0.1", 3000) - ], - } - client = aerospike.client(config) - - key = ("test", "demo", 1) - # "key_policy" is used instead of the correct key named "key" - policy = { - "key_policy": aerospike.POLICY_KEY_SEND - } - # This call will raise a ParamError from aerospike.exception - # Exception message should be: - # "key_policy" is an invalid policy dictionary key - client.get(key, policy=policy) + >> import aerospike + + >> config = { + ... "validate_keys": True, + ... "hosts": [ + ... ("127.0.0.1", 3000) + ... ], + ... } + >> client = aerospike.client(config) + + >> key = ("test", "demo", 1) + >> # "key_policy" is used instead of the correct key named "key" + >> policy = { + ... "key_policy": aerospike.POLICY_KEY_SEND + ... } + >> # This call will raise a ParamError from aerospike.exception + >> # Exception message should be: + >> # "key_policy" is an invalid policy dictionary key + >> client.get(key, policy=policy) * **hosts** (:class:`list`) A list of tuples identifying a node (or multiple nodes) in the cluster. From 50664bcaebb49fd8c084d3d096bf3284caa14a14 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:12:14 -0700 Subject: [PATCH 017/124] Fix doctest formatting. Not sure why sphinx didn't fail bc of this --- doc/aerospike.rst | 96 +++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 9c509b4c32..bc4ef18f63 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -100,12 +100,12 @@ Geospatial :param dict geo_data: a :class:`dict` representing the geospatial data. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - >> import aerospike + >>> import aerospike - >> # Create GeoJSON point using WGS84 coordinates. - >> latitude = 45.920278 - >> longitude = 63.342222 - >> loc = aerospike.geodata({'type': 'Point', 'coordinates': [longitude, latitude]}) + >>> # Create GeoJSON point using WGS84 coordinates. + >>> latitude = 45.920278 + >>> longitude = 63.342222 + >>> loc = aerospike.geodata({'type': 'Point', 'coordinates': [longitude, latitude]}) .. versionadded:: 1.0.54 @@ -117,10 +117,10 @@ Geospatial :param dict geojson_str: a :class:`str` of raw GeoJSON. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - >> import aerospike + >>> import aerospike - >> # Create GeoJSON point using WGS84 coordinates. - >> loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') + >>> # Create GeoJSON point using WGS84 coordinates. + >>> loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') .. versionadded:: 1.0.54 @@ -144,17 +144,17 @@ Types :return: a type representing a wildcard value. - >> import aerospike - >> from aerospike_helpers.operations import list_operations as list_ops + >>> import aerospike + >>> from aerospike_helpers.operations import list_operations as list_ops - >> client = aerospike.client({'hosts': [('localhost', 3000)]}) - >> key = 'test', 'demo', 1 + >>> client = aerospike.client({'hosts': [('localhost', 3000)]}) + >>> key = 'test', 'demo', 1 - >> # get all values of the form [1, ...] from a list of lists. - >> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - >> # [1, 2, 3] and [1, 'a'] - >> operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] - >> _, _, bins = client.operate(key, operations) + >>> # get all values of the form [1, ...] from a list of lists. + >>> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + >>> # [1, 2, 3] and [1, 'a'] + >>> operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] + >>> _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -166,17 +166,17 @@ Types :return: a type representing an infinite value. - >> import aerospike - >> from aerospike_helpers.operations import list_operations as list_ops + >>> import aerospike + >>> from aerospike_helpers.operations import list_operations as list_ops - >> client = aerospike.client({'hosts': [('localhost', 3000)]}) - >> key = 'test', 'demo', 1 + >>> client = aerospike.client({'hosts': [('localhost', 3000)]}) + >>> key = 'test', 'demo', 1 - >> # get all values of the form [1, ...] from a list of lists. - >> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - >> # [1, 2, 3] and [1, 'a'] - >> operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] - >> _, _, bins = client.operate(key, operations) + >>> # get all values of the form [1, ...] from a list of lists. + >>> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + >>> # [1, 2, 3] and [1, 'a'] + >>> operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] + >>> _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -202,10 +202,10 @@ Serialization .. seealso:: To use this function with :meth:`Client.put`, \ the argument to the serializer parameter should be :const:`aerospike.SERIALIZER_USER`. - >> def my_serializer(val): + >>> def my_serializer(val): ... return json.dumps(val) - >> aerospike.set_serializer(my_serializer) + >>> aerospike.set_serializer(my_serializer) .. versionadded:: 1.0.39 @@ -322,11 +322,11 @@ Other :return: a RIPEMD-160 digest of the input tuple. :rtype: :class:`bytearray` - >> import aerospike - >> import pprint + >>> import aerospike + >>> import pprint - >> digest = aerospike.calc_digest("test", "demo", 1 ) - >> pp.pprint(digest) + >>> digest = aerospike.calc_digest("test", "demo", 1 ) + >>> pp.pprint(digest) .. _client_config: @@ -358,9 +358,9 @@ Only the `hosts` key is required; the rest of the keys are optional. Invalid client config example: - >> import aerospike + >>> import aerospike - >> config = { + >>> config = { ... "validate_keys": True, ... "hosts": [ ... ("127.0.0.1", 3000) @@ -369,32 +369,32 @@ Only the `hosts` key is required; the rest of the keys are optional. ... "username": "user", ... "password": "password" ... } - >> # This call will raise a ParamError from aerospike.exception - >> # Exception message should be: - >> # "username" is an invalid client config dictionary key - >> client = aerospike.client(config) + >>> # This call will raise a ParamError from aerospike.exception + >>> # Exception message should be: + >>> # "username" is an invalid client config dictionary key + >>> client = aerospike.client(config) Invalid policy example: - >> import aerospike + >>> import aerospike - >> config = { + >>> config = { ... "validate_keys": True, ... "hosts": [ ... ("127.0.0.1", 3000) ... ], ... } - >> client = aerospike.client(config) + >>> client = aerospike.client(config) - >> key = ("test", "demo", 1) - >> # "key_policy" is used instead of the correct key named "key" - >> policy = { + >>> key = ("test", "demo", 1) + >>> # "key_policy" is used instead of the correct key named "key" + >>> policy = { ... "key_policy": aerospike.POLICY_KEY_SEND ... } - >> # This call will raise a ParamError from aerospike.exception - >> # Exception message should be: - >> # "key_policy" is an invalid policy dictionary key - >> client.get(key, policy=policy) + >>> # This call will raise a ParamError from aerospike.exception + >>> # Exception message should be: + >>> # "key_policy" is an invalid policy dictionary key + >>> client.get(key, policy=policy) * **hosts** (:class:`list`) A list of tuples identifying a node (or multiple nodes) in the cluster. From 9ba17f01dc173c4e11949cdaeb3b834bde1ba204 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:31:02 -0700 Subject: [PATCH 018/124] Fix reported failures. Exception detail doesn't matter since it's a tuple that contains the line number where it happened which isn't important --- doc/aerospike.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index bc4ef18f63..9d546d9e23 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -206,6 +206,7 @@ Serialization ... return json.dumps(val) >>> aerospike.set_serializer(my_serializer) + 0 .. versionadded:: 1.0.39 @@ -325,8 +326,9 @@ Other >>> import aerospike >>> import pprint - >>> digest = aerospike.calc_digest("test", "demo", 1 ) - >>> pp.pprint(digest) + >>> digest = aerospike.calc_digest("test", "demo", 1) + >>> pprint.pprint(digest) + bytearray(b'\xb7\xf4\xb88\x89\xe2\xdag\xdeh>\x1d\xf6\x91\x9a\x1e\xac\xc4F\xc8') .. _client_config: @@ -369,10 +371,9 @@ Only the `hosts` key is required; the rest of the keys are optional. ... "username": "user", ... "password": "password" ... } - >>> # This call will raise a ParamError from aerospike.exception - >>> # Exception message should be: - >>> # "username" is an invalid client config dictionary key >>> client = aerospike.client(config) + Traceback (most recent call last): + aerospike.exception.ParamError: "username" is an invalid client config dictionary key Invalid policy example: @@ -391,10 +392,9 @@ Only the `hosts` key is required; the rest of the keys are optional. >>> policy = { ... "key_policy": aerospike.POLICY_KEY_SEND ... } - >>> # This call will raise a ParamError from aerospike.exception - >>> # Exception message should be: - >>> # "key_policy" is an invalid policy dictionary key >>> client.get(key, policy=policy) + Traceback (most recent call last): + aerospike.exception.ParamError: "key_policy" is an invalid policy dictionary key * **hosts** (:class:`list`) A list of tuples identifying a node (or multiple nodes) in the cluster. From 14d5820b6b7b4b301ff3d16e6e03adc7770e06a8 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:49:17 -0700 Subject: [PATCH 019/124] Add custom script to run all doctest code examples. This allows us to run boilerplate code before each example in client.rst --- .github/workflows/smoke-tests.yml | 3 ++- doc/client.rst | 10 ++++------ doc/doctest.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 doc/doctest.py diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 03b8abb867..43445e92b0 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -422,7 +422,8 @@ jobs: - if: ${{ matrix.type == 'doctest' }} name: Run doctest - run: python3 -m doctest -v doc/aerospike.rst + run: python3 -m doctest -v doctest.py + working-directory: doc test-ee: runs-on: ubuntu-22.04 diff --git a/doc/client.rst b/doc/client.rst index 329ab109b7..4d33f0dc11 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -434,12 +434,10 @@ String Operations :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python - - client.put(keyTuple, {'bin1': 'Martin Luther King'}) - client.append(keyTuple, 'bin1', ' jr.') - (_, _, bins) = client.get(keyTuple) - print(bins) # Martin Luther King jr. + >>> client.put(keyTuple, {'bin1': 'Martin Luther King'}) + >>> client.append(keyTuple, 'bin1', ' jr.') + >>> (_, _, bins) = client.get(keyTuple) + >>> print(bins) # Martin Luther King jr. .. method:: prepend(key, bin, val[, meta: dict[, policy: dict]]) diff --git a/doc/doctest.py b/doc/doctest.py new file mode 100644 index 0000000000..328427bf9b --- /dev/null +++ b/doc/doctest.py @@ -0,0 +1,20 @@ +import doctest +import unittest +import runpy + + +def custom_setup(test): + # This code runs before every doctest in the suite + # test.globs['shared_data'] = [1, 2, 3] + # TODO: should use file location and not cwd + runpy.run_path('./examples/boilerplate.py') + +def load_tests(loader, tests, ignore): + # Add setup and teardown logic here + # TODO: should use file location and not cwd + tests.addTests(doctest.DocFileSuite(["./aerospike.rst"])) + tests.addTests(doctest.DocFileSuite(["./client.rst"], setUp=custom_setup)) + return tests + +if __name__ == "__main__": + unittest.main() From 667460f445be76f8aa0d137e2441e7d16c5480fc Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:53:34 -0700 Subject: [PATCH 020/124] Mv script name to prevent shadowing doctest package --- .github/workflows/smoke-tests.yml | 2 +- doc/{doctest.py => check-code-examples-in-docs.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/{doctest.py => check-code-examples-in-docs.py} (100%) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 43445e92b0..d26d3b863e 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -422,7 +422,7 @@ jobs: - if: ${{ matrix.type == 'doctest' }} name: Run doctest - run: python3 -m doctest -v doctest.py + run: python3 -m doctest -v check-code-examples-in-docs.py working-directory: doc test-ee: diff --git a/doc/doctest.py b/doc/check-code-examples-in-docs.py similarity index 100% rename from doc/doctest.py rename to doc/check-code-examples-in-docs.py From 86b1f35a67e33f69f6af8a907be30d04c10c2504 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Sun, 26 Apr 2026 19:58:02 -0700 Subject: [PATCH 021/124] Fix workflow --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index d26d3b863e..fd6f56a2a4 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -422,7 +422,7 @@ jobs: - if: ${{ matrix.type == 'doctest' }} name: Run doctest - run: python3 -m doctest -v check-code-examples-in-docs.py + run: python3 check-code-examples-in-docs.py working-directory: doc test-ee: From 1758e561af02c9f40b289037cac6ef1ed324f66f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:38:29 -0700 Subject: [PATCH 022/124] Try using this method instead to run the boilerplate code before every code example. It doesn't seem possible with doctest + unittest --- doc/client.rst | 36 ++++++++++++++++++++++++++++++------ doc/examples/boilerplate.py | 20 -------------------- 2 files changed, 30 insertions(+), 26 deletions(-) delete mode 100644 doc/examples/boilerplate.py diff --git a/doc/client.rst b/doc/client.rst index 4d33f0dc11..eca06ca85a 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -31,8 +31,29 @@ Assume every in-line example runs this code beforehand: .. warning:: Only run example code on a brand new Aerospike server. This code deletes all records in the ``demo`` set! -.. include:: examples/boilerplate.py - :code: python + +.. testsetup:: + + # Imports + import aerospike + from aerospike import exception as ex + import sys + + # Configure the client + config = { + 'hosts': [ ('127.0.0.1', 3000)] + } + + # Create a client and connect it to the cluster + try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) + except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + + # Record key tuple: (namespace, set, key) + keyTuple = ('test', 'demo', 'key') Basic example: @@ -434,10 +455,13 @@ String Operations :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - >>> client.put(keyTuple, {'bin1': 'Martin Luther King'}) - >>> client.append(keyTuple, 'bin1', ' jr.') - >>> (_, _, bins) = client.get(keyTuple) - >>> print(bins) # Martin Luther King jr. + + .. doctest:: + + >>> client.put(keyTuple, {'bin1': 'Martin Luther King'}) + >>> client.append(keyTuple, 'bin1', ' jr.') + >>> (_, _, bins) = client.get(keyTuple) + >>> print(bins) # Martin Luther King jr. .. method:: prepend(key, bin, val[, meta: dict[, policy: dict]]) diff --git a/doc/examples/boilerplate.py b/doc/examples/boilerplate.py deleted file mode 100644 index 74a7adff80..0000000000 --- a/doc/examples/boilerplate.py +++ /dev/null @@ -1,20 +0,0 @@ -# Imports -import aerospike -from aerospike import exception as ex -import sys - -# Configure the client -config = { - 'hosts': [ ('127.0.0.1', 3000)] -} - -# Create a client and connect it to the cluster -try: - client = aerospike.client(config) - client.truncate('test', "demo", 0) -except ex.ClientError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - sys.exit(1) - -# Record key tuple: (namespace, set, key) -keyTuple = ('test', 'demo', 'key') From f7664a2eca86640661b49b886514fcc01d2bd53a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:47:29 -0700 Subject: [PATCH 023/124] Use doctest builder to run doctest in client.rst --- .github/workflows/smoke-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index fd6f56a2a4..7ae90ddc90 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -420,9 +420,13 @@ jobs: run: python -m pytest ./new_tests -vv -W error::pytest.PytestUnraisableExceptionWarning working-directory: test + - name: Install test dependencies + if: ${{ matrix.type == 'doctest' }} + run: pip install -r doc/requirements.txt + - if: ${{ matrix.type == 'doctest' }} name: Run doctest - run: python3 check-code-examples-in-docs.py + run: sphinx-build -b doctest working-directory: doc test-ee: From 0b3af6d5fcbe80ce682942618d69da628469c6f5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:50:42 -0700 Subject: [PATCH 024/124] Forgot that pinned sphinx version requires 3.12 or higher --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 7ae90ddc90..1314714006 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -365,7 +365,7 @@ jobs: ] include: - type: doctest - py-version: '3.10' + py-version: '3.12' fail-fast: false steps: From bebf00093f4858efd781b2cfb90d7b6c2d62caa0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:55:08 -0700 Subject: [PATCH 025/124] Fix syntax --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 1314714006..e8760c9901 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -426,7 +426,7 @@ jobs: - if: ${{ matrix.type == 'doctest' }} name: Run doctest - run: sphinx-build -b doctest + run: sphinx-build -b doctest . doctest working-directory: doc test-ee: From a59305f11f6954d5c50d41f22a28cf8710c3ea73 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:32:48 -0700 Subject: [PATCH 026/124] Noticed some aerospike.* methods in aerospike.rst are returning MagicMock() --- doc/conf.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 31a7c201e6..c393fc0327 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -2,14 +2,14 @@ import sys, os -try: - from unittest.mock import MagicMock -except ImportError: - try: - from mock import Mock as MagicMock - except ImportError as e: - print("mock is missing: pip install mock") - raise e +# try: +# from unittest.mock import MagicMock +# except ImportError: +# try: +# from mock import Mock as MagicMock +# except ImportError as e: +# print("mock is missing: pip install mock") +# raise e # 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 @@ -20,13 +20,13 @@ # see https://docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules -class Mock(MagicMock): - @classmethod - def __getattr__(cls, name): - return MagicMock() +# class Mock(MagicMock): +# @classmethod +# def __getattr__(cls, name): +# return MagicMock() -sys.modules.update({"aerospike": Mock()}) +# sys.modules.update({"aerospike": Mock()}) # sys.path.append(os.path.abspath('/usr/local/lib/python2.7/site-packages/aerospike-1.0.44-py2.7-macosx-10.9-x86_64.egg/')) From 52418becae4267f8974d03c2541a1d9c6461af04 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:49:10 -0700 Subject: [PATCH 027/124] Fix HyperLogLog code example --- aerospike_helpers/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 042b8437c6..74ff998035 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -20,7 +20,8 @@ class HyperLogLog(bytes): The constructor takes in any argument that the :class:`bytes` constructor takes in. - >>> h = HyperLogLog([1, 2, 3]) + >>> import aerospike + >>> h = aerospike.HyperLogLog([1, 2, 3]) >>> client.put(key, {"hyperloglog": h}) """ def __new__(cls, o) -> "HyperLogLog": From d04848c317e22c36ad3aa1235d113f980ed626b6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:51:33 -0700 Subject: [PATCH 028/124] Fix formatting. Output here is correct --- doc/client.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index eca06ca85a..5fd1cd9227 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -459,9 +459,12 @@ String Operations .. doctest:: >>> client.put(keyTuple, {'bin1': 'Martin Luther King'}) + 0 >>> client.append(keyTuple, 'bin1', ' jr.') + 0 >>> (_, _, bins) = client.get(keyTuple) - >>> print(bins) # Martin Luther King jr. + >>> print(bins) + {'bin1': 'Martin Luther King jr.'} .. method:: prepend(key, bin, val[, meta: dict[, policy: dict]]) From 2d047be5ecb1d5ccc282d1844ca15a5659b3bbe3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:03:48 -0700 Subject: [PATCH 029/124] Try testcode block, if it works then just move the other code examples to this format (takes less work) --- doc/client.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 5fd1cd9227..9b6978c0cc 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -480,12 +480,16 @@ String Operations :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: client.put(keyTuple, {'bin1': 'Freeman'}) client.prepend(keyTuple, 'bin1', ' Gordon ') (_, _, bins) = client.get(keyTuple) - print(bins) # Gordon Freeman + print(bins) + + .. testoutput:: + + {"bin1": "Gordon Freeman"} .. index:: single: Numeric Operations From efb9f786f27134538763d9c699ed391c37ee7893 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:07:53 -0700 Subject: [PATCH 030/124] Fix hyperloglog.. --- aerospike_helpers/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 74ff998035..f99911b83b 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -20,8 +20,8 @@ class HyperLogLog(bytes): The constructor takes in any argument that the :class:`bytes` constructor takes in. - >>> import aerospike - >>> h = aerospike.HyperLogLog([1, 2, 3]) + >>> from aerospike_helpers import HyperLogLog + >>> h = HyperLogLog([1, 2, 3]) >>> client.put(key, {"hyperloglog": h}) """ def __new__(cls, o) -> "HyperLogLog": From 153cfa775cabe61274f6f0d68b9520461c67a23a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:09:27 -0700 Subject: [PATCH 031/124] Think it's safe to remove code to run doctest if we are already using sphinx doctest plugin --- aerospike_helpers/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index f99911b83b..aa482023aa 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -36,7 +36,3 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.__repr__() - -if __name__ == "__main__": - import doctest - doctest.testmod(raise_on_error=True) From 522334872fe0732bde7269812bfbfea867e86949 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:11:15 -0700 Subject: [PATCH 032/124] Fix prepend code example --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index 9b6978c0cc..6c4e95abb9 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -483,7 +483,7 @@ String Operations .. testcode:: client.put(keyTuple, {'bin1': 'Freeman'}) - client.prepend(keyTuple, 'bin1', ' Gordon ') + client.prepend(keyTuple, 'bin1', 'Gordon ') (_, _, bins) = client.get(keyTuple) print(bins) From 71c6941d37cb3449595032b85dd81a285f021acd Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:30:36 -0700 Subject: [PATCH 033/124] Make most of the code examples in client.rst testable. --- doc/client.rst | 111 +++++++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 6c4e95abb9..e7b5b75d0c 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -515,7 +515,7 @@ Numeric Operations :param dict policy: optional :ref:`aerospike_operate_policies`. Note: the ``exists`` policy option may not be: ``aerospike.POLICY_EXISTS_CREATE_OR_REPLACE`` nor ``aerospike.POLICY_EXISTS_REPLACE`` :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: # Start with 100 lives client.put(keyTuple, {'lives': 100}) @@ -523,12 +523,17 @@ Numeric Operations # Gain health client.increment(keyTuple, 'lives', 10) (key, meta, bins) = client.get(keyTuple) - print(bins) # 110 + print("Lives:", bins) # Take damage client.increment(keyTuple, 'lives', -90) (key, meta, bins) = client.get(keyTuple) - print(bins) # 20 + print("Lives:", bins) + + .. testoutput:: + + Lives: 110 + Lives: 20 .. index:: single: List Operations @@ -603,8 +608,11 @@ User Defined Functions .. note:: To run this example, do not run the boilerplate code. - .. code-block:: python - :emphasize-lines: 5,9 + .. TODO - probably there is better syntax than using emphasize-lines with hardcoded numbers + + .. :emphasize-lines: 5,9 + + .. testcode:: import aerospike @@ -627,7 +635,7 @@ User Defined Functions :param dict policy: currently **timeout** in milliseconds is the available policy. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: client.udf_remove('my_module.lua') @@ -639,19 +647,22 @@ User Defined Functions :rtype: :class:`list` :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: print(client.udf_list()) - # [ - # {'content': bytearray(b''), - # 'hash': bytearray(b'195e39ceb51c110950bd'), - # 'name': 'my_udf1.lua', - # 'type': 0}, - # {'content': bytearray(b''), - # 'hash': bytearray(b'8a2528e8475271877b3b'), - # 'name': 'stream_udf.lua', - # 'type': 0} - # ] + + .. testoutput:: + + [ + {'content': bytearray(b''), + 'hash': bytearray(b'195e39ceb51c110950bd'), + 'name': 'my_udf1.lua', + 'type': 0}, + {'content': bytearray(b''), + 'hash': bytearray(b'8a2528e8475271877b3b'), + 'name': 'stream_udf.lua', + 'type': 0} + ] .. method:: udf_get(module: str[, language: int = aerospike.UDF_TYPE_LUA[, policy: dict]]) -> str @@ -747,12 +758,15 @@ Info Operations :return: a :class:`list` of node info dictionaries. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: # Assuming two nodes nodes = client.get_node_names() print(nodes) - # [{'address': '1.1.1.1', 'port': 3000, 'node_name': 'BCER199932C'}, {'address': '1.1.1.1', 'port': 3010, 'node_name': 'ADFFE7782CD'}] + + .. testoutput:: + + [{'address': '1.1.1.1', 'port': 3000, 'node_name': 'BCER199932C'}, {'address': '1.1.1.1', 'port': 3010, 'node_name': 'ADFFE7782CD'}] .. versionchanged:: 6.0.0 @@ -763,12 +777,15 @@ Info Operations :return: a :class:`list` of node address tuples. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: # Assuming two nodes nodes = client.get_nodes() print(nodes) - # [('127.0.0.1', 3000), ('127.0.0.1', 3010)] + + .. testoutput:: + + [('127.0.0.1', 3000), ('127.0.0.1', 3010)] .. versionchanged:: 3.0.0 @@ -797,11 +814,14 @@ Info Operations :rtype: :class:`dict` :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. code-block:: python + .. testcode:: response = client.info_all("namespaces") print(response) - # {'BB9020011AC4202': (None, 'test\n')} + + .. testoutput:: + + {'BB9020011AC4202': (None, 'test\n')} .. versionadded:: 3.0.0 @@ -946,7 +966,7 @@ Index Operations .. note:: Requires server version >= 3.8.0 - .. code-block:: python + .. testcode:: import aerospike @@ -1067,9 +1087,7 @@ Index Operations .. note:: Requires server version >= 3.7.0 - .. code-block:: python - - import aerospike + .. testcode:: client = aerospike.client({ 'hosts': [ ('127.0.0.1', 3000)]}) client.index_geo2dsphere_create('test', 'pads', 'loc', 'pads_loc_geo') @@ -1478,7 +1496,7 @@ Key Tuple * How to use the key tuple in a `put` operation * How to fetch the key tuple in a `get` operation - .. code-block:: python + .. testcode:: import aerospike @@ -1494,7 +1512,7 @@ Key Tuple keyTuple = (namespaceName, setName, primaryKeyName) # Insert a record - recordBins = {'bin1':0, 'bin2':1} + recordBins = {'bin1': 0, 'bin2': 1} client.put(keyTuple, recordBins) # Now fetch that record @@ -1505,13 +1523,14 @@ Key Tuple # and there is the record's digest print(key) - # Expected output: - # ('test', 'setname', None, bytearray(b'b\xc7[\xbb\xa4K\xe2\x9al\xd12!&\xbf<\xd9\xf9\x1bPo')) - # Cleanup client.remove(keyTuple) client.close() + .. testoutput:: + + ('test', 'setname', None, bytearray(b'b\xc7[\xbb\xa4K\xe2\x9al\xd12!&\xbf<\xd9\xf9\x1bPo')) + .. seealso:: `Data Model: Keys and Digests `_. .. _aerospike_record_tuple: @@ -1544,7 +1563,7 @@ Record Tuple We reuse the code example in the key-tuple section and print the ``meta`` and ``bins`` values that were returned from :meth:`~aerospike.Client.get()`: - .. code-block:: python + .. testcode:: import aerospike @@ -1559,25 +1578,27 @@ Record Tuple keyTuple = (namespaceName, setName, primaryKeyName) # Insert a record - recordBins = {'bin1':0, 'bin2':1} + recordBins = {'bin1': 0, 'bin2': 1} client.put(keyTuple, recordBins) # Now fetch that record (key, meta, bins) = client.get(keyTuple) # Generation is 1 because this is the first time we wrote the record - print(meta) - - # Expected output: - # {'ttl': 2592000, 'gen': 1} + print("Metadata:", meta) # The bin-value pairs we inserted - print(bins) - {'bin1': 0, 'bin2': 1} + print("Bins:", bins) client.remove(keyTuple) client.close() + .. testoutput:: + + Metadata: {'ttl': 2592000, 'gen': 1} + Bins: {'bin1': 0, 'bin2': 1} + + .. seealso:: `Data Model: Records `_. .. _metadata_dict: @@ -2329,7 +2350,7 @@ List Policies Example: - .. code-block:: python + .. testcode:: list_policy = { "write_flags": aerospike.LIST_WRITE_ADD_UNIQUE | aerospike.LIST_WRITE_INSERT_BOUNDED, @@ -2374,7 +2395,7 @@ Map Policies Example: - .. code-block:: python + .. testcode:: # Server >= 4.3.0 map_policy = { @@ -2404,7 +2425,7 @@ Bit Policies Example: - .. code-block:: python + .. testcode:: bit_policy = { 'bit_write_flags': aerospike.BIT_WRITE_UPDATE_ONLY @@ -2432,7 +2453,7 @@ HyperLogLog Policies Example: - .. code-block:: python + .. testcode:: HLL_policy = { 'flags': aerospike.HLL_WRITE_UPDATE_ONLY @@ -2516,6 +2537,8 @@ Partition Objects Default: ``{}`` (All partitions will be queried/scanned). + .. TODO more thorough example needed here. + .. code-block:: python # Example of a query policy using partition_filter. From 03fbd1318fcd074cfb38c2bfd12d1f8cfdde4fa4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:44:50 -0700 Subject: [PATCH 034/124] Test if this command works in aerospike_helpers --- aerospike_helpers/cdt_ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 7aa758fd40..ba0a04b502 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -17,7 +17,7 @@ Helper functions to generate complex data type context (cdt_ctx) objects for use with operations on nested CDTs (list, map, etc). -Example:: +.. testcode:: import aerospike from aerospike import exception as ex From 6e869378c5a6d65c3e5679c968709cd32721817d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:56:45 -0700 Subject: [PATCH 035/124] Try this setting in order to build docs for aerospike_helpers. --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index c393fc0327..c603734228 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,7 @@ # return MagicMock() -# sys.modules.update({"aerospike": Mock()}) +autodoc_mock_imports = ["aerospike"] # sys.path.append(os.path.abspath('/usr/local/lib/python2.7/site-packages/aerospike-1.0.44-py2.7-macosx-10.9-x86_64.egg/')) From 7cb953d5e0562e3c02581d334d7fede70964e4b4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:07:19 -0700 Subject: [PATCH 036/124] Add back example label. --- aerospike_helpers/cdt_ctx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index ba0a04b502..f8d30eb84d 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -17,6 +17,8 @@ Helper functions to generate complex data type context (cdt_ctx) objects for use with operations on nested CDTs (list, map, etc). +Example: + .. testcode:: import aerospike From 8ddf4989c0ab26b874c68e7ffd9896a6297cdd7a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:13:21 -0700 Subject: [PATCH 037/124] Replace 'Example::' with testcode directive for sphinx doctest to check --- aerospike_helpers/batch/records.py | 20 ++- aerospike_helpers/expressions/arithmetic.py | 57 +++++-- aerospike_helpers/expressions/base.py | 160 +++++++++++++----- aerospike_helpers/expressions/bitwise.py | 76 ++++++--- .../expressions/bitwise_operators.py | 40 +++-- aerospike_helpers/expressions/hll.py | 36 +++- aerospike_helpers/expressions/list.py | 124 ++++++++++---- aerospike_helpers/expressions/map.py | 148 ++++++++++++---- aerospike_helpers/metrics/__init__.py | 4 +- .../operations/bitwise_operations.py | 8 +- .../operations/expression_operations.py | 8 +- .../operations/hll_operations.py | 4 +- 12 files changed, 514 insertions(+), 171 deletions(-) diff --git a/aerospike_helpers/batch/records.py b/aerospike_helpers/batch/records.py index 5b0a8cfd92..92dff87be8 100644 --- a/aerospike_helpers/batch/records.py +++ b/aerospike_helpers/batch/records.py @@ -81,7 +81,9 @@ def __init__( self, key: tuple, ops: "TypeOps", meta: Optional[dict] = None, policy: "TypeBatchPolicyWrite" = None ) -> None: """ - Example:: + Example: + + .. testcode:: # Create a batch Write to increment bin "a" by 10 and read the result from the record. import aerospike @@ -140,7 +142,9 @@ def __init__( policy: "TypeBatchPolicyRead" = None, ) -> None: """ - Example:: + Example: + + .. testcode:: # Create a batch Read to read bin "a" from the record. import aerospike @@ -192,7 +196,9 @@ def __init__( self, key: tuple, module: str, function: str, args: "TypeUDFArgs", policy: "TypeBatchPolicyApply" = None ) -> None: """ - Example:: + Example: + + .. testcode:: # Create a batch Apply to apply UDF "test_func" to bin "a" from the record. # Assume that "test_func" takes a bin name string as an argument. @@ -240,7 +246,9 @@ class Remove(BatchRecord): def __init__(self, key: tuple, policy: "TypeBatchPolicyRemove" = None) -> None: """ - Example:: + Example: + + .. testcode:: # Create a batch Remove to remove the record. import aerospike_helpers.operations as op @@ -278,7 +286,9 @@ class BatchRecords: def __init__(self, batch_records: Optional[TypeBatchRecordList] = None) -> None: """ - Example:: + Example: + + .. testcode:: import aerospike import aerospike_helpers.operations.operations as op diff --git a/aerospike_helpers/expressions/arithmetic.py b/aerospike_helpers/expressions/arithmetic.py index 8ec1012e17..d231e1a47b 100644 --- a/aerospike_helpers/expressions/arithmetic.py +++ b/aerospike_helpers/expressions/arithmetic.py @@ -53,7 +53,10 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value). - Example:: + Example: + + .. testcode:: + # Integer bin "a" + "b" == 11 expr = exp.Eq(exp.Add(exp.IntBin("a"), exp.IntBin("b")), 11).compile() @@ -86,7 +89,9 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value) - Example:: + Example: + + .. testcode:: # Integer bin "a" - "b" == 11 expr = exp.Eq(exp.Sub(exp.IntBin("a"), exp.IntBin("b")), 11).compile() @@ -117,7 +122,9 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value) - Example:: + Example: + + .. testcode:: # Integer bin "a" * "b" >= 11 expr = exp.GE(exp.Mul(exp.IntBin("a"), exp.IntBin("b")), 11).compile() @@ -150,7 +157,9 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value) - Example:: + Example: + + .. testcode:: # Integer bin "a" / "b" / "c" >= 11 expr = exp.GE(exp.Div(exp.IntBin("a"), exp.IntBin("b"), exp.IntBin("c")), 11).compile() @@ -184,7 +193,9 @@ def __init__(self, base: "TypeFloat", exponent: "TypeFloat"): :return: (float value) - Example:: + Example: + + .. testcode:: # Float bin "a" ** 2.0 == 16.0 expr = exp.Eq(exp.Pow(exp.FloatBin("a"), 2.0), 16.0).compile() @@ -212,7 +223,9 @@ def __init__(self, num: "TypeFloat", base: "TypeFloat"): :return: (float value) - Example:: + Example: + + .. testcode:: # For float bin "a", log("a", 2.0) == 16.0 expr = exp.Eq(exp.Log(exp.FloatBin("a"), 2.0), 16.0).compile() @@ -240,7 +253,9 @@ def __init__(self, numerator: "TypeInteger", denominator: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # For int bin "a" % 10 == 0 expr = exp.Eq(exp.Mod(exp.IntBin("a"), 10), 0).compile() @@ -270,7 +285,9 @@ def __init__(self, value: "TypeNumber"): :return: (number value) - Example:: + Example: + + .. testcode:: # For int bin "a", abs("a") == 1 expr = exp.Eq(exp.Abs(exp.IntBin("a")), 1).compile() @@ -300,7 +317,9 @@ def __init__(self, value: "TypeFloat"): :return: (float value) - Example:: + Example: + + .. testcode:: # Floor(2.25) == 2.0 expr = exp.Eq(exp.Floor(2.25), 2.0).compile() @@ -331,7 +350,9 @@ def __init__(self, value: "TypeFloat"): :return: (float value) - Example:: + Example: + + .. testcode:: # Ceil(2.25) == 3.0 expr = exp.Eq(exp.Ceil(2.25), 3.0).compile() @@ -357,7 +378,9 @@ def __init__(self, value: "TypeFloat"): :return: (integer value) - Example:: + Example: + + .. testcode:: #For float bin "a", int(exp.FloatBin("a")) == 2 expr = exp.Eq(exp.ToInt(exp.FloatBin("a")), 2).compile() @@ -379,7 +402,9 @@ def __init__(self, value: "TypeInteger"): :return: (float value) - Example:: + Example: + + .. testcode:: #For int bin "a", float(exp.IntBin("a")) == 2 expr = exp.Eq(exp.ToFloat(exp.IntBin("a")), 2).compile() @@ -404,7 +429,9 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value). - Example:: + Example: + + .. testcode:: # for integer bins a, b, c, min(a, b, c) > 0 expr = exp.GT(exp.Min(exp.IntBin("a"), exp.IntBin("b"), exp.IntBin("c")), 0).compile() @@ -428,7 +455,9 @@ def __init__(self, *args: "TypeNumber"): :return: (integer or float value). - Example:: + Example: + + .. testcode:: # for integer bins a, b, c, max(a, b, c) > 100 expr = exp.GT(exp.Max(exp.IntBin("a"), exp.IntBin("b"), exp.IntBin("c")), 100).compile() diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index e4280fb642..84f5efa17b 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -85,7 +85,9 @@ class Unknown(_BaseExpr): def __init__(self): """:return: (unknown value) - Example:: + Example: + + .. testcode:: from aerospike_helpers.expressions.arithmetic import Add @@ -122,7 +124,9 @@ class KeyInt(_Key): def __init__(self): """:return: (integer value): Integer value of the key if the key is an integer. - Example:: + Example: + + .. testcode:: # Integer record key >= 10000. expr = exp.GE(exp.KeyInt(), 10000).compile() @@ -140,7 +144,9 @@ class KeyStr(_Key): def __init__(self): """:return: (string value): string value of the key if the key is an string. - Example:: + Example: + + .. testcode:: # string record key == "aaa". expr = exp.Eq(exp.KeyStr(), "aaa").compile() @@ -158,7 +164,9 @@ class KeyBlob(_Key): def __init__(self): """:return: (blob value): Blob value of the key if the key is a blob. - Example:: + Example: + + .. testcode:: # blob record key <= bytearray([0x65, 0x65]). expr = exp.GE(exp.KeyBlob(), bytearray([0x65, 0x65])).compile() @@ -178,7 +186,9 @@ class KeyExists(_BaseExpr): def __init__(self): """:return: (boolean value): True if the record has a stored key, false otherwise. - Example:: + Example: + + .. testcode:: # Key exists in record meta data. expr = exp.KeyExists().compile() @@ -205,7 +215,9 @@ def __init__(self, bin: str): :return: (boolean bin) - Example:: + Example: + + .. testcode:: # Boolean bin "a" is True. expr = exp.BoolBin("a").compile() @@ -227,7 +239,9 @@ def __init__(self, bin: str): :return: (integer bin) - Example:: + Example: + + .. testcode:: # Integer bin "a" == 200. expr = exp.Eq(exp.IntBin("a"), 200).compile() @@ -249,7 +263,9 @@ def __init__(self, bin: str): :return: (string bin) - Example:: + Example: + + .. testcode:: # String bin "a" == "xyz". expr = exp.Eq(exp.StrBin("a"), "xyz").compile() @@ -271,7 +287,9 @@ def __init__(self, bin: str): :return: (float bin) - Example:: + Example: + + .. testcode:: # Float bin "a" > 2.71. expr = exp.GT(exp.FloatBin("a"), 2.71).compile() @@ -293,7 +311,9 @@ def __init__(self, bin: str): :return: (blob bin) - Example:: + Example: + + .. testcode:: #. Blob bin "a" == bytearray([0x65, 0x65]) expr = exp.Eq(exp.BlobBin("a"), bytearray([0x65, 0x65])).compile() @@ -315,7 +335,9 @@ def __init__(self, bin: str): :return: (GeoJSON bin) - Example:: + Example: + + .. testcode:: #GeoJSON bin "a" contained by GeoJSON bin "b". expr = exp.CmpGeo(GeoBin("a"), exp.GeoBin("b")).compile() @@ -337,7 +359,9 @@ def __init__(self, bin: str): :return: (list bin) - Example:: + Example: + + .. testcode:: from aerospike_helpers.expressions import list as list_exprs @@ -367,7 +391,9 @@ def __init__(self, bin: str): :return: (map bin) - Example:: + Example: + + .. testcode:: from aerospike_helpers.expressions import map as map_exprs @@ -391,7 +417,9 @@ def __init__(self, bin: str): :return: (HyperLogLog bin) - Example:: + Example: + + .. testcode:: # Does HLL bin "a" have a hll_count > 1000000. from aerospike_helpers.expressions import hll @@ -413,7 +441,9 @@ def __init__(self, bin: str): :return: (boolean value): True if bin exists, False otherwise. - Example:: + Example: + + .. testcode:: #Bin "a" exists in record. expr = exp.BinExists("a").compile() @@ -435,7 +465,9 @@ def __init__(self, bin: str): :return: (integer value): returns the bin type. - Example:: + Example: + + .. testcode:: # bin "a" == type string. expr = exp.Eq(exp.BinType("a"), aerospike.AS_BYTES_STRING).compile() @@ -460,7 +492,9 @@ class SetName(_BaseExpr): def __init__(self): """:return: (string value): Name of the set this record belongs to. - Example:: + Example: + + .. testcode:: # Record set name == "myset". expr = exp.Eq(exp.SetName(), "myset").compile() @@ -486,7 +520,9 @@ class DeviceSize(_BaseExpr): def __init__(self): """:return: (integer value): Uncompressed storage size of the record. - Example:: + Example: + + .. testcode:: # Record device size >= 100 KB. expr = exp.GE(exp.DeviceSize(), 100 * 1024).compile() @@ -543,7 +579,9 @@ class LastUpdateTime(_BaseExpr): def __init__(self): """:return: (integer value): When the record was last updated. - Example:: + Example: + + .. testcode:: # Record last update time >= 2020-01-15. expr = exp.GE(exp.LastUpdateTime(), 1577836800).compile() @@ -562,7 +600,9 @@ class SinceUpdateTime(_BaseExpr): def __init__(self): """:return: (integer value): Number of milliseconds since last updated. - Example:: + Example: + + .. testcode:: # Record last updated more than 2 hours ago. expr = exp.GT(exp.SinceUpdateTime(), 2 * 60 * 60 * 1000).compile() @@ -581,7 +621,9 @@ class VoidTime(_BaseExpr): def __init__(self): """:return: (integer value): Expiration time in nanoseconds since 1970-01-01. - Example:: + Example: + + .. testcode:: # Record expires on 2021-01-01. expr = exp.And( @@ -603,7 +645,9 @@ def __init__(self): """:return: (integer value): Number of seconds till the record will expire, returns -1 if the record never expires. - Example:: + Example: + + .. testcode:: # Record expires in less than 1 hour. expr = exp.LT(exp.TTL(), 60 * 60).compile() @@ -623,7 +667,9 @@ class IsTombstone(_BaseExpr): def __init__(self): """:return: (boolean value): True if the record is a tombstone, false otherwise. - Example:: + Example: + + .. testcode:: # Detect deleted records that are in tombstone state. expr = exp.IsTombstone().compile() @@ -643,7 +689,9 @@ def __init__(self, mod: int): :return: (integer value): Value in range 0 and mod (exclusive). - Example:: + Example: + + .. testcode:: # Records that have digest(key) % 3 == 1. expr = exp.Eq(exp.DigestMod(3), 1).compile() @@ -668,7 +716,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" == 11 expr = exp.Eq(exp.IntBin("a"), 11).compile() @@ -688,7 +738,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" not == 13. expr = exp.NE(exp.IntBin("a"), 13).compile() @@ -708,7 +760,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" > 8. expr = exp.GT(exp.IntBin("a"), 8).compile() @@ -728,7 +782,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" >= 88. expr = exp.GE(exp.IntBin("a"), 88).compile() @@ -748,7 +804,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" < 1000. expr = exp.LT(exp.IntBin("a"), 1000).compile() @@ -768,7 +826,9 @@ def __init__(self, expr0: "TypeComparisonArg", expr1: "TypeComparisonArg"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Integer bin "a" <= 1. expr = exp.LE(exp.IntBin("a"), 1).compile() @@ -789,7 +849,9 @@ def __init__(self, options: int, regex_str: str, cmp_str: Union[_BaseExpr, str]) :return: (boolean value) - Example:: + Example: + + .. testcode:: # Select string bin "a" that starts with "prefix" and ends with "suffix". # Ignore case and do not match newline. @@ -813,7 +875,9 @@ def __init__(self, expr0: "TypeGeo", expr1: "TypeGeo"): :return: (boolean value) - Example:: + Example: + + .. testcode:: # Geo bin "point" is within geo bin "region". expr = exp.CmpGeo(exp.GeoBin("point"), exp.GeoBin("region")).compile() @@ -837,7 +901,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (boolean value) - Example:: + Example: + + .. testcode:: # not (a == 0 or a == 10) expr = exp.Not(exp.Or( @@ -858,7 +924,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (boolean value) - Example:: + Example: + + .. testcode:: # (a > 5 || a == 0) && b < 3 expr = exp.And( @@ -881,7 +949,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (boolean value) - Example:: + Example: + + .. testcode:: # (a == 0 || b == 0) expr = exp.Or( @@ -902,7 +972,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (boolean value) - Example:: + Example: + + .. testcode:: # exclusive(a == 0, b == 0) expr = exp.Exclusive( @@ -942,7 +1014,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (boolean value) - Example:: + Example: + + .. testcode:: from aerospike_helpers.expressions.arithmetic import Add, Sub, Mul @@ -1010,7 +1084,9 @@ def __init__(self, *exprs: _BaseExpr): :return: (result of scoped expression) - Example:: + Example: + + .. testcode:: # for int bin "a", 5 < a < 10 expr = exp.Let(exp.Def("x", exp.IntBin("a")), @@ -1036,7 +1112,9 @@ def __init__(self, var_name: str, expr: _BaseExpr): :return: (a variable name expression pair) - Example:: + Example: + + .. testcode:: # for int bin "a", 5 < a < 10 expr = exp.Let(exp.Def("x", exp.IntBin("a")), @@ -1062,7 +1140,9 @@ def __init__(self, var_name: str): :return: (value stored in variable) - Example:: + Example: + + .. testcode:: # for int bin "a", 5 < a < 10 expr = exp.Let(exp.Def("x", exp.IntBin("a")), diff --git a/aerospike_helpers/expressions/bitwise.py b/aerospike_helpers/expressions/bitwise.py index ea9f29450d..f2e641babe 100644 --- a/aerospike_helpers/expressions/bitwise.py +++ b/aerospike_helpers/expressions/bitwise.py @@ -19,7 +19,9 @@ :mod:`Bitwise Operations API ` for binary data. -Example:: +Example: + +.. testcode:: import aerospike_helpers.expressions as exp # Let blob bin "c" == bytearray([3] * 5). @@ -60,7 +62,9 @@ def __init__(self, policy: "TypePolicy", byte_size: int, flags: int, bin: "TypeB :return: Blob value expression of resized blob bin. - Example:: + Example: + + .. testcode:: # Blob bin "c" == bytearray([1] * 5). # Resize blob bin "c" from the front so that the returned value is bytearray([0] * 5 + [1] * 5). @@ -96,7 +100,9 @@ def __init__(self, policy: "TypePolicy", byte_offset: int, value: "TypeBitValue" :return: Resulting blob containing the inserted bytes. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # Insert 3 so that returned value is bytearray([1, 3, 1, 1, 1, 1]). @@ -130,7 +136,9 @@ def __init__(self, policy: "TypePolicy", byte_offset: int, byte_size: int, bin: :return: Resulting blob containing the remaining bytes. - Example:: + Example: + + .. testcode:: # b = bytearray([1, 2, 3, 4, 5]) expr = exp.BitRemove(None, 1, 1, exp.BlobBin("b")).compile() @@ -169,7 +177,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, value: :return: Resulting blob expression with the bits overwritten. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([0] * 5). # Set bit at offset 7 with size 1 bits to 1 to make the returned value bytearray([1, 0, 0, 0, 0]). @@ -205,7 +215,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, value: :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # bitwise Or `8` with the first byte of blob bin c so that the returned value is bytearray([9, 1, 1, 1, 1]). @@ -248,7 +260,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, value: :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # bitwise Xor `1` with the first byte of blob bin c so that the returned value is bytearray([0, 1, 1, 1, 1]) @@ -284,7 +298,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, value: :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # bitwise and `0` with the first byte of blob bin c so that the returned value is bytearray([0, 1, 1, 1, 1]) @@ -327,7 +343,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, bin: "T :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([255] * 5). # bitwise, not, all of "c" to get bytearray([254] * 5). @@ -362,7 +380,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, shift: :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # Bit left shift the first byte of bin "c" to get bytearray([8, 1, 1, 1, 1]). @@ -398,7 +418,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, shift: :return: Resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([8] * 5). # Bit left shift the first byte of bin "c" to get bytearray([4, 8, 8, 8, 8]). @@ -446,7 +468,9 @@ def __init__( :return: resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Assume we have a blob bin of five bytes: bytearray([1, 1, 1, 1, 1]) expr = exp.BitAdd(None, 8, 8, 1, aerospike.BIT_OVERFLOW_FAIL, exp.BlobBin("b")).compile() @@ -498,7 +522,9 @@ def __init__( :return: resulting blob with the bits operated on. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1] * 5). # Bit subtract the second byte of bin "c" to get bytearray([1, 0, 1, 1, 1]) @@ -539,7 +565,9 @@ def __init__(self, policy: "TypePolicy", bit_offset: int, bit_size: int, value: :return: Resulting blob expression with the bits overwritten. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([0] * 5). # Set bit at offset 7 with size 1 bytes to 1 to make the returned value bytearray([1, 0, 0, 0, 0]). @@ -578,7 +606,9 @@ def __init__(self, bit_offset: int, bit_size: int, bin: "TypeBinName"): :return: Blob, bit_size bits rounded up to the nearest byte size. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1, 2, 3, 4, 5). # Get 2 from bin "c". @@ -600,7 +630,9 @@ def __init__(self, bit_offset: int, bit_size: int, bin: "TypeBinName"): :return: Blob, bit_size bits rounded up to the nearest byte size. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([3] * 5). # Count set bits starting at 3rd byte in bin "c" to get count of 6. @@ -627,7 +659,9 @@ def __init__(self, bit_offset: int, bit_size: int, value: bool, bin: "TypeBinNam :return: Index of the left most bit starting from bit_offset set to value. Returns -1 if not found. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([3] * 5). # Scan the first byte of bin "c" for the first bit set to 1. (should get 6) @@ -650,7 +684,9 @@ def __init__(self, bit_offset: int, bit_size: int, value: bool, bin: "TypeBinNam :return: Index of the right most bit starting from bit_offset set to value. Returns -1 if not found. - Example:: + Example: + + .. testcode:: # b = bytearray([1, 0, 0, 0, 128]) expr = exp.BitRightScan(32, 8, True, exp.BlobBin("b")).compile() @@ -677,7 +713,9 @@ def __init__(self, bit_offset: int, bit_size: int, sign: bool, bin: "TypeBinName :return: Integer expression. - Example:: + Example: + + .. testcode:: # Let blob bin "c" == bytearray([1, 2, 3, 4, 5). # Get 2 as an integer from bin "c". diff --git a/aerospike_helpers/expressions/bitwise_operators.py b/aerospike_helpers/expressions/bitwise_operators.py index 73165defc1..aeca79b00f 100644 --- a/aerospike_helpers/expressions/bitwise_operators.py +++ b/aerospike_helpers/expressions/bitwise_operators.py @@ -48,7 +48,9 @@ def __init__(self, *exprs: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", a & 0xff == 0x11 expr = exp.Eq(exp.IntAnd(exp.IntBin("a"), 0xff), 0x11).compile() @@ -71,7 +73,9 @@ def __init__(self, *exprs: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", a | 0x10 not == 0 expr = exp.NE(exp.IntOr(IntBin("a"), 0x10), 0).compile() @@ -94,7 +98,9 @@ def __init__(self, *exprs: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", "b", a ^ b == 16 expr = exp.Eq(exp.IntXOr(exp.IntBin("a"), exp.IntBin("b")), 16).compile() @@ -116,7 +122,9 @@ def __init__(self, expr: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", ~ a == 7 expr = exp.Eq(exp.IntNot(exp.IntBin("a")), 7).compile() @@ -139,7 +147,9 @@ def __init__(self, value: "TypeInteger", shift: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", a << 8 > 0xff expr = exp.GT(exp.IntLeftShift(exp.IntBin("a"), 8), 0xff).compile() @@ -162,7 +172,9 @@ def __init__(self, value: "TypeInteger", shift: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", a >>> 8 > 0xff expr = exp.GT(exp.IntRightShift(exp.IntBin("a"), 8), 0xff).compile() @@ -185,7 +197,9 @@ def __init__(self, value: "TypeInteger", shift: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", a >> 8 > 0xff expr = exp.GT(exp.IntArithmeticRightShift(exp.IntBin("a"), 8), 0xff).compile() @@ -207,7 +221,9 @@ def __init__(self, value: "TypeInteger"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", count(a) == 4 expr = exp.Eq(exp.IntCount(exp.IntBin("a")), 4).compile() @@ -234,7 +250,9 @@ def __init__(self, value: "TypeInteger", search: "TypeBool"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", lscan(a, True) == 4 expr = exp.GT(exp.IntLeftScan(exp.IntBin("a"), True), 4).compile() @@ -262,7 +280,9 @@ def __init__(self, value: "TypeInteger", search: "TypeBool"): :return: (integer value) - Example:: + Example: + + .. testcode:: # for int bin "a", rscan(a, True) == 4 expr = exp.GT(exp.IntRightScan(exp.IntBin("a"), True), 4).compile() diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 3905e5f075..bcb364573d 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -61,7 +61,9 @@ def __init__( :return: Returns the resulting hll. - Example:: + Example: + + .. testcode:: # Create an HLL with 12 index bits and 24 min hash bits. expr = exp.HLLInit(None, 12, 24, exp.HLLBin("my_hll")) @@ -96,7 +98,9 @@ def __init__( :return: Returns the resulting hll bin after adding elements from list. - Example:: + Example: + + .. testcode:: # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. # Add ['key4', 'key5', 'key6'] so that the returned value is ['key1', 'key2', 'key3', 'key4', 'key5', @@ -128,7 +132,9 @@ def __init__(self, bin: "TypeBinName"): :return: Integer bin, the estimated number of unique elements in an HLL. - Example:: + Example: + + .. testcode:: # Get count from HLL bin "d". expr = exp.HLLGetCount(exp.HLLBin("d")).compile() @@ -148,7 +154,9 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): :return: HLL bin representing the set union. - Example:: + Example: + + .. testcode:: # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing HLL objects retrieved from the aerospike database. @@ -173,7 +181,9 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): :return: Integer bin, estimated number of elements in the set union. - Example:: + Example: + + .. testcode:: # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. @@ -198,7 +208,9 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): :return: Integer bin, estimated number of elements in the set intersection. - Example:: + Example: + + .. testcode:: # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. @@ -223,7 +235,9 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): :return: Float bin, estimated similarity between 0.0 and 1.0. - Example:: + Example: + + .. testcode:: # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. @@ -248,7 +262,9 @@ def __init__(self, bin: "TypeBinName"): :return: List bin, a list containing the index_bit_count and minhash_bit_count. - Example:: + Example: + + .. testcode:: # Get description of HLL bin "d". expr = exp.HLLDescribe(exp.HLLBin("d")).compile() @@ -274,7 +290,9 @@ def __init__( :return: 1 if bin may contain any key in list, 0 otherwise. - Example:: + Example: + + .. testcode:: # Check if HLL bin "d" may contain any of the keys in `list`. expr = exp.HLLMayContain(["key1", "key2", "key3"], exp.HLLBin("d")).compile() diff --git a/aerospike_helpers/expressions/list.py b/aerospike_helpers/expressions/list.py index 5efbb4df38..d3aaaecb15 100644 --- a/aerospike_helpers/expressions/list.py +++ b/aerospike_helpers/expressions/list.py @@ -59,7 +59,9 @@ def __init__(self, ctx: "TypeCTX", policy: "TypePolicy", value: "TypeValue", bin :return: List expression. - Example:: + Example: + + .. testcode:: # Check if length of list bin "a" is > 5 after appending 1 item. listAppendedBy3 = exp.ListAppend(None, None, 3, exp.ListBin("a")) @@ -97,7 +99,9 @@ def __init__(self, ctx: "TypeCTX", policy: "TypePolicy", value: "TypeValue", bin :return: List expression. - Example:: + Example: + + .. testcode:: # Check if length of list bin "a" is > 5 after appending multiple items. listAppendedByTwoItems = exp.ListAppendItems(None, None, [3, 2], exp.ListBin("a")) @@ -140,7 +144,9 @@ def __init__( :return: List expression. - Example:: + Example: + + .. testcode:: # Check if list bin "a" has length > 5 after insert. listInsertedBy3At0 = exp.ListInsert(None, None, 0, 3, exp.ListBin("a")) @@ -182,7 +188,9 @@ def __init__( :return: List expression. - Example:: + Example: + + .. testcode:: # Check if list bin "a" has length > 5 after inserting items. listInsertedByTwoItems = exp.ListInsertItems(None, None, 0, [4, 7], exp.ListBin("a")) @@ -224,7 +232,9 @@ def __init__( :return: List expression. - Example:: + Example: + + .. testcode:: # Check if incremented value in list bin "a" is the largest in the list. # Rank of -1 == largest element @@ -273,7 +283,9 @@ def __init__( :return: List expression. - Example:: + Example: + + .. testcode:: # Get smallest element in list bin "a" after setting index 1 to 10. listSetAtIndex1 = exp.ListSet(None, None, 1, 10, exp.ListBin("a")) @@ -311,7 +323,9 @@ def __init__(self, ctx: "TypeCTX", bin: "TypeBinName"): :return: List expression. - Example:: + Example: + + .. testcode:: # Clear list value of list nested in list bin "a" index 1. from aerospike_helpers import cdt_ctx @@ -341,7 +355,9 @@ def __init__(self, ctx: "TypeCTX", order: int, bin: "TypeBinName"): :return: list expression. - Example:: + Example: + + .. testcode:: # Get value of sorted list bin "a". expr = exp.ListSort(None, aerospike.LIST_SORT_DEFAULT, "a").compile() @@ -369,7 +385,9 @@ def __init__(self, ctx: "TypeCTX", value: "TypeValue", bin: "TypeBinName", inver :return: list expression. - Example:: + Example: + + .. testcode:: # See if list bin "a", with `3` removed, is equal to list bin "b". listRemoved3 = exp.ListRemoveByValue(None, 3, exp.ListBin("a")) @@ -400,7 +418,9 @@ def __init__(self, ctx: "TypeCTX", values: "TypeListValue", bin: "TypeBinName", :return: list expression. - Example:: + Example: + + .. testcode:: # Remove elements with values [1, 2, 3] from list bin "a". expr = exp.ListRemoveByValueList(None, [1, 2, 3], exp.ListBin("a")).compile() @@ -441,7 +461,9 @@ def __init__( :return: list expression. - Example:: + Example: + + .. testcode:: # Remove list of items with values >= 3 and < 7 from list bin "a". expr = exp.ListRemoveByValueRange(None, 3, 7, exp.ListBin("a")).compile() @@ -482,7 +504,9 @@ def __init__( :return: list expression. - Example:: + Example: + + .. testcode:: # Remove elements larger than 4 by relative rank in list bin "a". # Assume list in bin a is: [6, 12, 4, 21] @@ -527,7 +551,9 @@ def __init__( :return: list expression. - Example:: + Example: + + .. testcode:: # Remove 2 elements greater than 4 # Assume list in bin a is: [6, 12, 4, 21] @@ -558,7 +584,9 @@ def __init__(self, ctx: "TypeCTX", index: "TypeIndex", bin: "TypeBinName"): :return: list expression. - Example:: + Example: + + .. testcode:: # Get size of list bin "a" after index 3 has been removed. expr = exp.ListSize(None, exp.ListRemoveByIndex(None, 3, exp.ListBin("a"))).compile() @@ -586,7 +614,9 @@ def __init__(self, ctx: "TypeCTX", index: "TypeIndex", bin: "TypeBinName", inver :return: list expression. - Example:: + Example: + + .. testcode:: # Remove all elements starting from index 3 in list bin "a". expr = exp.ListRemoveByIndexRangeToEnd(None, 3, exp.ListBin("a")).compile() @@ -624,7 +654,9 @@ def __init__( :return: list expression. - Example:: + Example: + + .. testcode:: # Get size of list bin "a" after index 3, 4, and 5 have been removed. expr = exp.ListSize(None, exp.ListRemoveByIndexRange(None, 3, 3, exp.ListBin("a"))).compile() @@ -653,7 +685,9 @@ def __init__(self, ctx: "TypeCTX", rank: "TypeRank", bin: "TypeBinName"): :return: list expression. - Example:: + Example: + + .. testcode:: # Remove smallest value in list bin "a". expr = exp.ListRemoveByRank(None, 0, exp.ListBin("a")).compile() @@ -681,7 +715,9 @@ def __init__(self, ctx: "TypeCTX", rank: "TypeRank", bin: "TypeBinName", inverte :return: list expression. - Example:: + Example: + + .. testcode:: # Remove the 2 largest elements from List bin "a". # Assume list bin contains [6, 12, 4, 21] @@ -721,7 +757,9 @@ def __init__( :return: list expression. - Example:: + Example: + + .. testcode:: # Remove the 3 smallest items from list bin "a". expr = exp.ListRemoveByRankRange(None, 0, 3, exp.ListBin("a")).compile() @@ -754,7 +792,9 @@ def __init__(self, ctx: "TypeCTX", bin: "TypeBinName"): :return: Integer expression. - Example:: + Example: + + .. testcode:: #Take the size of list bin "a". expr = exp.ListSize(None, exp.ListBin("a")).compile() @@ -793,7 +833,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the index of the element with value, 3, in list bin "a". expr = exp.ListGetByValue(None, aerospike.LIST_RETURN_INDEX, 3, exp.ListBin("a")).compile() @@ -839,7 +881,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get rank of values between 3 (inclusive) and 7 (exclusive) in list bin "a". expr = exp.ListGetByValueRange(None, aerospike.LIST_RETURN_RANK, 3, 7, exp.ListBin("a")).compile() @@ -882,7 +926,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the indexes of the the elements in list bin "a" with values [3, 6, 12]. expr = exp.ListGetByValueList(None, aerospike.LIST_RETURN_INDEX, [3, 6, 12], exp.ListBin("a")).compile() @@ -923,7 +969,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # [6, 12, 4, 21] expr = exp.ListGetByValueRelRankRangeToEnd(None, aerospike.LIST_RETURN_VALUE, 3, 1, @@ -993,7 +1041,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # [6, 12, 4, 21] expr = exp.ListGetByValueRelRankRange(None, aerospike.LIST_RETURN_VALUE, 3, 1, 2, @@ -1039,7 +1089,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the value at index 0 in list bin "a". (assume this value is an integer) expr = exp.ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, @@ -1079,7 +1131,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get element 5 to end from list bin "a". expr = exp.ListGetByIndexRangeToEnd(None, aerospike.LIST_RETURN_VALUE, 5, exp.ListBin("a")).compile() @@ -1122,7 +1176,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements at indexes 3, 4, 5, 6 in list bin "a". expr = exp.ListGetByIndexRange(None, aerospike.LIST_RETURN_VALUE, 3, 4, exp.ListBin("a")).compile() @@ -1163,7 +1219,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the smallest element in list bin "a". expr = exp.ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, @@ -1196,7 +1254,9 @@ def __init__(self, ctx: "TypeCTX", return_type: int, rank: "TypeRank", bin: "Typ :return: Expression. - Example:: + Example: + + .. testcode:: # Get the three largest elements in list bin "a". expr = exp.ListGetByRankRangeToEnd(None, aerospike.LIST_RETURN_VALUE, -3, exp.ListBin("a")).compile() @@ -1238,7 +1298,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the 3 smallest elements in list bin "a". expr = exp.ListGetByRankRange(None, aerospike.LIST_RETURN_VALUE, 0, 3, exp.ListBin("a")).compile() diff --git a/aerospike_helpers/expressions/map.py b/aerospike_helpers/expressions/map.py index ac6623705f..4509565903 100644 --- a/aerospike_helpers/expressions/map.py +++ b/aerospike_helpers/expressions/map.py @@ -62,7 +62,9 @@ def __init__(self, ctx: "TypeCTX", policy: "TypePolicy", key: "TypeKey", value: :return: Map expression. - Example:: + Example: + + .. testcode:: # Put {"key": 27} into map bin "b". expr = exp.MapPut(None, None, "key", 27, exp.MapBin("b")).compile() @@ -100,7 +102,9 @@ def __init__(self, ctx: "TypeCTX", policy: "TypePolicy", map: map, bin: "TypeBin :return: Map expression. - Example:: + Example: + + .. testcode:: # Put {27: 'key27', 28: 'key28'} into map bin "b". expr = exp.MapPutItems(None, None, {27: 'key27', 28: 'key28'}, exp.MapBin("b")).compile() @@ -140,7 +144,9 @@ def __init__(self, ctx: "TypeCTX", policy: "TypePolicy", key: "TypeKey", value: :return: Map expression. - Example:: + Example: + + .. testcode:: # Increment element at 'vageta' in map bin "b" by 9000. expr = exp.MapIncrement(None, None, 'vageta', 9000, exp.MapBin("b")).compile() @@ -176,7 +182,9 @@ def __init__(self, ctx: "TypeCTX", bin: "TypeBinName"): :return: Map expression. - Example:: + Example: + + .. testcode:: # Clear map bin "b". expr = exp.MapClear(None, exp.MapBin("b")).compile() @@ -203,7 +211,9 @@ def __init__(self, ctx: "TypeCTX", key: "TypeKey", bin: "TypeBinName"): :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove element at key 1 in map bin "b". expr = exp.MapRemoveByKey(None, 1, exp.MapBin("b")).compile() @@ -234,7 +244,9 @@ def __init__(self, ctx: "TypeCTX", keys: List[TypeKey], bin: "TypeBinName", inve :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove elements at keys [1, 2] in map bin "b". expr = exp.MapRemoveByKeyList(None, [1, 2], exp.MapBin("b")).compile() @@ -276,7 +288,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove elements at keys between 1 and 10 in map bin "b". expr = exp.MapRemoveByKeyRange(None, 1, 10, exp.MapBin("b")).compile() @@ -312,7 +326,9 @@ def __init__(self, ctx: "TypeCTX", key: "TypeKey", index: "TypeIndex", bin: "Typ :return: Map expression. - Example:: + Example: + + .. testcode:: # {"key1": 1, "key2": 2, "key3": 3, "key4": 10} expr = exp.MapRemoveByKeyRelIndexRangeToEnd(None, "key2", 1, exp.MapBin("b")).compile() @@ -355,7 +371,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove the next two items after key1 # {"key1": 1, "key2": 2, "key3": 3, "key4": 10} @@ -390,7 +408,9 @@ def __init__(self, ctx: "TypeCTX", value: "TypeValue", bin: "TypeBinName", inver :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove {"key1": 1} from map bin "b". expr = exp.MapRemoveByValue(None, 1, exp.MapBin("b")).compile() @@ -421,7 +441,9 @@ def __init__(self, ctx: "TypeCTX", values: "TypeListValue", bin: "TypeBinName", :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove elements with values 1, 2, 3 from map bin "b". expr = exp.MapRemoveByValueList(None, [1, 2, 3], exp.MapBin("b")).compile() @@ -463,7 +485,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove list of items with values >= 3 and < 7 from map bin "b". expr = exp.MapRemoveByValueRange(None, 3, 7, exp.MapBin("b")).compile() @@ -502,7 +526,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove all elements with values larger than 3 from map bin "b". expr = exp.MapRemoveByValueRelRankRangeToEnd(None, 3, 1, exp.MapBin("b")).compile() @@ -546,7 +572,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove the key with a value just lower than 17 expr = exp.MapRemoveByValueRelRankRange(None, 17, -1, 1, exp.MapBin("b")).compile() @@ -578,7 +606,9 @@ def __init__(self, ctx: "TypeCTX", index: "TypeIndex", bin: "TypeBinName"): :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove element with smallest key from map bin "b". expr = exp.MapRemoveByIndex(None, 0, exp.MapBin("b")).compile() @@ -609,7 +639,9 @@ def __init__(self, ctx: "TypeCTX", index: "TypeIndex", bin: "TypeBinName", inver :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove all elements starting from index 3 in map bin "b". expr = exp.MapRemoveByIndexRangeToEnd(None, 3, exp.MapBin("b")).compile() @@ -648,7 +680,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Get size of map bin "b" after index 3, 4, and 5 have been removed. expr = exp.MapSize(None, exp.MapRemoveByIndexRange(None, 3, 3, exp.MapBin("b"))).compile() @@ -679,7 +713,9 @@ def __init__(self, ctx: "TypeCTX", rank: "TypeRank", bin: "TypeBinName"): :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove key with smallest value in map bin "b". expr = exp.MapRemoveByRank(None, 0, exp.MapBin("b")).compile() @@ -710,7 +746,9 @@ def __init__(self, ctx: "TypeCTX", rank: "TypeRank", bin: "TypeBinName", inverte :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove keys with 2 largest values from map bin "b". expr = exp.MapRemoveByRankRangeToEnd(None, -2, exp.MapBin("b")).compile() @@ -749,7 +787,9 @@ def __init__( :return: Map expression. - Example:: + Example: + + .. testcode:: # Remove 3 keys with the smallest values from map bin "b". expr = exp.MapRemoveByRankRange(None, 0, 3, exp.MapBin("b")).compile() @@ -784,7 +824,9 @@ def __init__(self, ctx: "TypeCTX", bin: "TypeBinName"): :return: Integer expression. - Example:: + Example: + + .. testcode:: #Take the size of map bin "b". expr = exp.MapSize(None, exp.MapBin("b")).compile() @@ -816,7 +858,9 @@ def __init__(self, ctx: "TypeCTX", return_type: int, value_type: int, key: "Type :return: Expression. - Example:: + Example: + + .. testcode:: # Get the value at key "key0" in map bin "b". (assume the value at key0 is an integer) expr = exp.MapGetByKey(None, aerospike.MAP_RETURN_VALUE, exp.ResultType.INTEGER, "key0", @@ -863,7 +907,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements at keys "key3", "key4", "key5", "key6" in map bin "b". expr = exp.MapGetByKeyRange(None, aerospike.MAP_RETURN_VALUE, "key3", "key7", exp.MapBin("b")).compile() @@ -908,7 +954,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements at keys "key3", "key4", "key5" in map bin "b". expr = exp.MapGetByKeyList(None, aerospike.MAP_RETURN_VALUE, ["key3", "key4", "key5"], @@ -955,7 +1003,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements with keys larger than "key2" from map bin "b". expr = exp.MapGetByKeyRelIndexRangeToEnd(None, aerospike.MAP_RETURN_VALUE, "key2", 1, @@ -1005,7 +1055,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: expr = exp.MapGetByKeyRelIndexRange(None, aerospike.MAP_RETURN_VALUE, "key2", 0, 2, exp.MapBin("b")).compile() @@ -1056,7 +1108,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the rank of the element with value, 3, in map bin "b". expr = exp.MapGetByValue(None, aerospike.MAP_RETURN_RANK, 3, exp.MapBin("b")).compile() @@ -1101,7 +1155,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements with values between 3 and 7 from map bin "b". expr = exp.MapGetByValueRange(None, aerospike.MAP_RETURN_VALUE, 3, 7, exp.MapBin("b")).compile() @@ -1145,7 +1201,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the indexes of the the elements in map bin "b" with values [3, 6, 12]. expr = exp.MapGetByValueList(None, aerospike.MAP_RETURN_INDEX, [3, 6, 12], exp.MapBin("b")).compile() @@ -1188,7 +1246,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the values of all elements in map bin "b" larger than 3. expr = exp.MapGetByValueRelRankRangeToEnd(None, aerospike.MAP_RETURN_VALUE, 3, 1, exp.MapBin("b")).compile() @@ -1233,7 +1293,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # {"key1": 1, "key2": 2, "key3": 3, "key4": 10} # Get next two largest values greater than a value of 1 @@ -1276,7 +1338,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the value at index 0 in map bin "b". (assume this value is an integer) expr = exp.MapGetByIndex(None, aerospike.MAP_RETURN_VALUE, @@ -1316,7 +1380,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get element at index 5 to end from map bin "b". expr = exp.MapGetByIndexRangeToEnd(None, aerospike.MAP_RETURN_VALUE, 5, exp.MapBin("b")).compile() @@ -1359,7 +1425,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get elements at indexes 3, 4, 5, 6 in map bin "b". expr = exp.MapGetByIndexRange(None, aerospike.MAP_RETURN_VALUE, 3, 4, exp.MapBin("b")).compile() @@ -1400,7 +1468,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the smallest element in map bin "b". expr = exp.MapGetByRank(None, aerospike.MAP_RETURN_VALUE, exp.ResultType.INTEGER, 0, @@ -1433,7 +1503,9 @@ def __init__(self, ctx: "TypeCTX", return_type: int, rank: "TypeRank", bin: "Typ :return: Expression. - Example:: + Example: + + .. testcode:: # Get the three largest elements in map bin "b". expr = exp.MapGetByRankRangeToEnd(None, aerospike.MAP_RETURN_VALUE, -3, MapBin("b")).compile() @@ -1476,7 +1548,9 @@ def __init__( :return: Expression. - Example:: + Example: + + .. testcode:: # Get the 3 smallest elements in map bin "b". expr = exp.MapGetByRankRange(None, aerospike.MAP_RETURN_VALUE, 0, 3, exp.MapBin("b")).compile() diff --git a/aerospike_helpers/metrics/__init__.py b/aerospike_helpers/metrics/__init__.py index 2f5ceb5c87..a64a624ccb 100644 --- a/aerospike_helpers/metrics/__init__.py +++ b/aerospike_helpers/metrics/__init__.py @@ -217,7 +217,9 @@ class MetricsPolicy: The bucket units are in milliseconds. The first 2 buckets are "<=1ms" and ">1ms". labels (dict[str, str]): List of name/value labels that is applied when exporting metrics. - Example:: + Example: + + .. testcode:: # latencyColumns=7 latencyShift=1 # <=1ms >1ms >2ms >4ms >8ms >16ms >32ms diff --git a/aerospike_helpers/operations/bitwise_operations.py b/aerospike_helpers/operations/bitwise_operations.py index f0677bbe28..67b48c1b9e 100644 --- a/aerospike_helpers/operations/bitwise_operations.py +++ b/aerospike_helpers/operations/bitwise_operations.py @@ -30,7 +30,9 @@ * -1: rightmost bit in the map * -4: 3 bits from rightmost -Example:: +Example: + +.. testcode:: import aerospike from aerospike_helpers.operations import bitwise_operations @@ -77,7 +79,9 @@ client.remove(key) client.close() -Example:: +Example: + +.. testcode:: import aerospike from aerospike import exception as e diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index da5ec131ab..ee77276afe 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -47,7 +47,9 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r Returns: A dictionary to be passed to operate or operate_ordered. - Example:: + Example: + + .. testcode:: # Read the value of int bin "balance". # Let 'client' be a connected aerospike client. @@ -89,7 +91,9 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ Returns: A dictionary to be passed to operate or operate_ordered. - Example:: + Example: + + .. testcode:: # Write the value of int bin "balance" + 50 back to "balance". # Let 'client' be a connected aerospike client. diff --git a/aerospike_helpers/operations/hll_operations.py b/aerospike_helpers/operations/hll_operations.py index 7e91fbcc8b..73ea55de5f 100644 --- a/aerospike_helpers/operations/hll_operations.py +++ b/aerospike_helpers/operations/hll_operations.py @@ -29,7 +29,9 @@ .. seealso:: `HyperLogLog (Data Type) more info. \ `_. -Example:: +Example: + +.. testcode:: import aerospike from aerospike_helpers.operations import hll_operations as hll_ops From 66dcce0c866dddd5844c2677a411f8c7db9cc460 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:36:38 -0700 Subject: [PATCH 038/124] Revert code examples back to code-output-style blocks since sphinx doctest plugin supports that style --- doc/aerospike.rst | 160 +++++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 9d546d9e23..986914af41 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -45,17 +45,17 @@ Client Simple example: -.. TODO - Multiline comments maybe should be added to API descriptions - - >>> import aerospike - >>> # Configure the client to first connect to a cluster node at 127.0.0.1 - >>> # The client will learn about the other nodes in the cluster from the seed node. - >>> # Also sets a top level policy for read commands - >>> config = { - ... 'hosts': [ ('127.0.0.1', 3000) ], - ... 'policies': {'read': {'total_timeout': 1000}} - ... } - >>> client = aerospike.client(config) + .. testcode:: + + import aerospike + # Configure the client to first connect to a cluster node at 127.0.0.1 + # The client will learn about the other nodes in the cluster from the seed node. + # Also sets a top level policy for read commands + config = { + 'hosts': [ ('127.0.0.1', 3000) ], + 'policies': {'read': {'total_timeout': 1000}} + } + client = aerospike.client(config) Connecting using TLS example: @@ -100,12 +100,14 @@ Geospatial :param dict geo_data: a :class:`dict` representing the geospatial data. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - >>> import aerospike + .. testcode:: + + import aerospike - >>> # Create GeoJSON point using WGS84 coordinates. - >>> latitude = 45.920278 - >>> longitude = 63.342222 - >>> loc = aerospike.geodata({'type': 'Point', 'coordinates': [longitude, latitude]}) + # Create GeoJSON point using WGS84 coordinates. + latitude = 45.920278 + longitude = 63.342222 + loc = aerospike.geodata({'type': 'Point', 'coordinates': [longitude, latitude]}) .. versionadded:: 1.0.54 @@ -117,10 +119,12 @@ Geospatial :param dict geojson_str: a :class:`str` of raw GeoJSON. :return: an instance of the :py:class:`aerospike.GeoJSON` class. - >>> import aerospike + .. testcode:: - >>> # Create GeoJSON point using WGS84 coordinates. - >>> loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') + import aerospike + + # Create GeoJSON point using WGS84 coordinates. + loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') .. versionadded:: 1.0.54 @@ -144,17 +148,19 @@ Types :return: a type representing a wildcard value. - >>> import aerospike - >>> from aerospike_helpers.operations import list_operations as list_ops + .. testcode:: - >>> client = aerospike.client({'hosts': [('localhost', 3000)]}) - >>> key = 'test', 'demo', 1 + import aerospike + from aerospike_helpers.operations import list_operations as list_ops - >>> # get all values of the form [1, ...] from a list of lists. - >>> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - >>> # [1, 2, 3] and [1, 'a'] - >>> operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] - >>> _, _, bins = client.operate(key, operations) + client = aerospike.client({'hosts': [('localhost', 3000)]}) + key = 'test', 'demo', 1 + + # get all values of the form [1, ] from a list of lists. + # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + # [1, 2, 3] and [1, 'a'] + operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] + _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -166,17 +172,19 @@ Types :return: a type representing an infinite value. - >>> import aerospike - >>> from aerospike_helpers.operations import list_operations as list_ops + .. testcode:: - >>> client = aerospike.client({'hosts': [('localhost', 3000)]}) - >>> key = 'test', 'demo', 1 + import aerospike + from aerospike_helpers.operations import list_operations as list_ops - >>> # get all values of the form [1, ...] from a list of lists. - >>> # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - >>> # [1, 2, 3] and [1, 'a'] - >>> operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] - >>> _, _, bins = client.operate(key, operations) + client = aerospike.client({'hosts': [('localhost', 3000)]}) + key = 'test', 'demo', 1 + + # get all values of the form [1, ] from a list of lists. + # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match + # [1, 2, 3] and [1, 'a'] + operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] + _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -202,10 +210,12 @@ Serialization .. seealso:: To use this function with :meth:`Client.put`, \ the argument to the serializer parameter should be :const:`aerospike.SERIALIZER_USER`. - >>> def my_serializer(val): - ... return json.dumps(val) + .. testcode:: + + def my_serializer(val): + return json.dumps(val) - >>> aerospike.set_serializer(my_serializer) + aerospike.set_serializer(my_serializer) 0 .. versionadded:: 1.0.39 @@ -323,11 +333,13 @@ Other :return: a RIPEMD-160 digest of the input tuple. :rtype: :class:`bytearray` - >>> import aerospike - >>> import pprint + .. testcode:: - >>> digest = aerospike.calc_digest("test", "demo", 1) - >>> pprint.pprint(digest) + import aerospike + import pprint + + digest = aerospike.calc_digest("test", "demo", 1) + pprint.pprint(digest) bytearray(b'\xb7\xf4\xb88\x89\xe2\xdag\xdeh>\x1d\xf6\x91\x9a\x1e\xac\xc4F\xc8') .. _client_config: @@ -360,39 +372,43 @@ Only the `hosts` key is required; the rest of the keys are optional. Invalid client config example: - >>> import aerospike - - >>> config = { - ... "validate_keys": True, - ... "hosts": [ - ... ("127.0.0.1", 3000) - ... ], - ... # The correct key is "user", but "username" may be used by accident - ... "username": "user", - ... "password": "password" - ... } - >>> client = aerospike.client(config) + .. testcode:: + + import aerospike + + config = { + "validate_keys": True, + "hosts": [ + ("127.0.0.1", 3000) + ], + # The correct key is "user", but "username" may be used by accident + "username": "user", + "password": "password" + } + client = aerospike.client(config) Traceback (most recent call last): aerospike.exception.ParamError: "username" is an invalid client config dictionary key Invalid policy example: - >>> import aerospike - - >>> config = { - ... "validate_keys": True, - ... "hosts": [ - ... ("127.0.0.1", 3000) - ... ], - ... } - >>> client = aerospike.client(config) - - >>> key = ("test", "demo", 1) - >>> # "key_policy" is used instead of the correct key named "key" - >>> policy = { - ... "key_policy": aerospike.POLICY_KEY_SEND - ... } - >>> client.get(key, policy=policy) + .. testcode:: + + import aerospike + + config = { + "validate_keys": True, + "hosts": [ + ("127.0.0.1", 3000) + ], + } + client = aerospike.client(config) + + key = ("test", "demo", 1) + # "key_policy" is used instead of the correct key named "key" + policy = { + "key_policy": aerospike.POLICY_KEY_SEND + } + client.get(key, policy=policy) Traceback (most recent call last): aerospike.exception.ParamError: "key_policy" is an invalid policy dictionary key From 0620de53e7f4f8fb1b0edc7ba4d788f4f902f2e3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:38:21 -0700 Subject: [PATCH 039/124] Rm unused script now that we use sphinx doctest --- doc/check-code-examples-in-docs.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 doc/check-code-examples-in-docs.py diff --git a/doc/check-code-examples-in-docs.py b/doc/check-code-examples-in-docs.py deleted file mode 100644 index 328427bf9b..0000000000 --- a/doc/check-code-examples-in-docs.py +++ /dev/null @@ -1,20 +0,0 @@ -import doctest -import unittest -import runpy - - -def custom_setup(test): - # This code runs before every doctest in the suite - # test.globs['shared_data'] = [1, 2, 3] - # TODO: should use file location and not cwd - runpy.run_path('./examples/boilerplate.py') - -def load_tests(loader, tests, ignore): - # Add setup and teardown logic here - # TODO: should use file location and not cwd - tests.addTests(doctest.DocFileSuite(["./aerospike.rst"])) - tests.addTests(doctest.DocFileSuite(["./client.rst"], setUp=custom_setup)) - return tests - -if __name__ == "__main__": - unittest.main() From ad7b685f3449ceda56841dc9060e83579c959c6c Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:20:27 -0700 Subject: [PATCH 040/124] Fix code example outputs causing doc build to fail --- doc/aerospike.rst | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 986914af41..ec0279228b 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -216,7 +216,6 @@ Serialization return json.dumps(val) aerospike.set_serializer(my_serializer) - 0 .. versionadded:: 1.0.39 @@ -340,7 +339,10 @@ Other digest = aerospike.calc_digest("test", "demo", 1) pprint.pprint(digest) - bytearray(b'\xb7\xf4\xb88\x89\xe2\xdag\xdeh>\x1d\xf6\x91\x9a\x1e\xac\xc4F\xc8') + + .. testoutput:: + + bytearray(b'\xb7\xf4\xb88\x89\xe2\xdag\xdeh>\x1d\xf6\x91\x9a\x1e\xac\xc4F\xc8') .. _client_config: @@ -386,8 +388,11 @@ Only the `hosts` key is required; the rest of the keys are optional. "password": "password" } client = aerospike.client(config) - Traceback (most recent call last): - aerospike.exception.ParamError: "username" is an invalid client config dictionary key + + .. testoutput:: + + Traceback (most recent call last): + aerospike.exception.ParamError: "username" is an invalid client config dictionary key Invalid policy example: @@ -409,8 +414,11 @@ Only the `hosts` key is required; the rest of the keys are optional. "key_policy": aerospike.POLICY_KEY_SEND } client.get(key, policy=policy) - Traceback (most recent call last): - aerospike.exception.ParamError: "key_policy" is an invalid policy dictionary key + + .. testoutput:: + + Traceback (most recent call last): + aerospike.exception.ParamError: "key_policy" is an invalid policy dictionary key * **hosts** (:class:`list`) A list of tuples identifying a node (or multiple nodes) in the cluster. From 6e839832248d9dd5236a8b150a1f04bab5365fee Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:26:18 -0700 Subject: [PATCH 041/124] Add testsetup block to fix many errors in aerospike_helpers.expressions docs --- doc/aerospike_helpers.expressions.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/aerospike_helpers.expressions.rst b/doc/aerospike_helpers.expressions.rst index 53e3fcfbf3..b32f11690f 100644 --- a/doc/aerospike_helpers.expressions.rst +++ b/doc/aerospike_helpers.expressions.rst @@ -157,7 +157,9 @@ The following documentation uses type aliases that map to standard Python types. .. note:: Requires server version >= 5.2.0 -Assume all in-line examples run this code beforehand:: +Assume all in-line examples run this code beforehand: + +.. testsetup:: import aerospike import aerospike_helpers.expressions as exp From 72615a1794185e72775f8c981686f49f864cfaee Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:34:18 -0700 Subject: [PATCH 042/124] Fix a few bad/inconsistent code examples --- aerospike_helpers/batch/records.py | 6 +++--- aerospike_helpers/expressions/base.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aerospike_helpers/batch/records.py b/aerospike_helpers/batch/records.py index 92dff87be8..87a583f632 100644 --- a/aerospike_helpers/batch/records.py +++ b/aerospike_helpers/batch/records.py @@ -87,7 +87,7 @@ def __init__( # Create a batch Write to increment bin "a" by 10 and read the result from the record. import aerospike - import aerospike_helpers.operations as op + from aerospike_helpers.operations import operations as op from aerospike_helpers.batch.records import Write bin_name = "a" @@ -148,7 +148,7 @@ def __init__( # Create a batch Read to read bin "a" from the record. import aerospike - import aerospike_helpers.operations as op + from aerospike_helpers.operations import operations as op from aerospike_helpers.batch.records import Read bin_name = "a" @@ -259,7 +259,7 @@ def __init__(self, key: tuple, policy: "TypeBatchPolicyRemove" = None) -> None: user_key = 1 key = (namespace, set, user_key) - br = Remove(key, ops) + br = Remove(key) """ super().__init__(key) self._type = _Types.REMOVE diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 84f5efa17b..3527976451 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -340,7 +340,7 @@ def __init__(self, bin: str): .. testcode:: #GeoJSON bin "a" contained by GeoJSON bin "b". - expr = exp.CmpGeo(GeoBin("a"), exp.GeoBin("b")).compile() + expr = exp.CmpGeo(exp.GeoBin("a"), exp.GeoBin("b")).compile() """ self._fixed = {_Keys.BIN_KEY: bin} From f966c32a4dd4871bfd9b15b46c8e7cfb4787e5b0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:41:01 -0700 Subject: [PATCH 043/124] Fix more bad code examples.. --- aerospike_helpers/expressions/bitwise.py | 2 +- aerospike_helpers/expressions/list.py | 2 +- aerospike_helpers/expressions/map.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/expressions/bitwise.py b/aerospike_helpers/expressions/bitwise.py index f2e641babe..9e23103f2d 100644 --- a/aerospike_helpers/expressions/bitwise.py +++ b/aerospike_helpers/expressions/bitwise.py @@ -528,7 +528,7 @@ def __init__( # Let blob bin "c" == bytearray([1] * 5). # Bit subtract the second byte of bin "c" to get bytearray([1, 0, 1, 1, 1]) - expr = exp.BitSubtract(None, 8, 8, 1, aerospike.BIT_OVERFLOW_FAIL).compile() + expr = exp.BitSubtract(None, 8, 8, 1, aerospike.BIT_OVERFLOW_FAIL, "c").compile() """ self._children = ( bit_offset, diff --git a/aerospike_helpers/expressions/list.py b/aerospike_helpers/expressions/list.py index d3aaaecb15..4566716ec8 100644 --- a/aerospike_helpers/expressions/list.py +++ b/aerospike_helpers/expressions/list.py @@ -238,7 +238,7 @@ def __init__( # Check if incremented value in list bin "a" is the largest in the list. # Rank of -1 == largest element - largestListValue = exp.ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, -1) + largestListValue = exp.ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, -1, "a") listIncrementedAtIndex1 = exp.ListIncrement(None, None, 1, 5, exp.ListBin("a")) listItemAtIndex1 = exp.ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 1, listIncrementedAtIndex1) diff --git a/aerospike_helpers/expressions/map.py b/aerospike_helpers/expressions/map.py index 4509565903..cd9c08b1ec 100644 --- a/aerospike_helpers/expressions/map.py +++ b/aerospike_helpers/expressions/map.py @@ -1508,7 +1508,7 @@ def __init__(self, ctx: "TypeCTX", return_type: int, rank: "TypeRank", bin: "Typ .. testcode:: # Get the three largest elements in map bin "b". - expr = exp.MapGetByRankRangeToEnd(None, aerospike.MAP_RETURN_VALUE, -3, MapBin("b")).compile() + expr = exp.MapGetByRankRangeToEnd(None, aerospike.MAP_RETURN_VALUE, -3, exp.MapBin("b")).compile() """ self._children = (rank, bin if isinstance(bin, _BaseExpr) else MapBin(bin)) self._fixed = {_Keys.RETURN_TYPE_KEY: return_type} From 16d0bbbf9a1e88fff17e8c45babd9e672692ee5b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:06:42 -0700 Subject: [PATCH 044/124] Improve naming for one code example. Fix another example --- aerospike_helpers/expressions/bitwise_operators.py | 2 +- aerospike_helpers/operations/expression_operations.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/expressions/bitwise_operators.py b/aerospike_helpers/expressions/bitwise_operators.py index aeca79b00f..65089ab4ff 100644 --- a/aerospike_helpers/expressions/bitwise_operators.py +++ b/aerospike_helpers/expressions/bitwise_operators.py @@ -78,7 +78,7 @@ def __init__(self, *exprs: "TypeInteger"): .. testcode:: # for int bin "a", a | 0x10 not == 0 - expr = exp.NE(exp.IntOr(IntBin("a"), 0x10), 0).compile() + expr = exp.NE(exp.IntOr(exp.IntBin("a"), 0x10), 0).compile() """ self._children = exprs + (_GenericExpr(_ExprOp._AS_EXP_CODE_END_OF_VA_ARGS, 0, {}),) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index ee77276afe..0573006bf7 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -99,12 +99,12 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ # Let 'client' be a connected aerospike client. # Let int bin 'balance' == 50. - from aerospike_helpers.operations import expression_operations as expressions + from aerospike_helpers.operations import expression_operations as expr_ops from aerospike_helpers.expressions import * expr = Add(IntBin("balance"), 50).compile() ops = [ - expressions.expression_write("balance", expr) + expr_ops.expression_write("balance", expr) ] client.operate(self.key, ops) _, _, res = client.get(self.key) From ef4ef1fa9195a0f9376cc7a3367ec49c9130b8cc Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:12:21 -0700 Subject: [PATCH 045/124] Add missing import --- aerospike_helpers/batch/records.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aerospike_helpers/batch/records.py b/aerospike_helpers/batch/records.py index 87a583f632..c4bf02810b 100644 --- a/aerospike_helpers/batch/records.py +++ b/aerospike_helpers/batch/records.py @@ -204,6 +204,7 @@ def __init__( # Assume that "test_func" takes a bin name string as an argument. # Assume the appropriate UDF module has already been registered. import aerospike_helpers.operations as op + from aerospike_helpers.batch.records import Apply module = "my_lua" From 817450d1502b597f0f246225c9c66e40c1cdf606 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:33:30 -0700 Subject: [PATCH 046/124] Add debug print to see why operation code is not an integer --- doc/aerospike.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index ec0279228b..2185118c22 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -160,6 +160,8 @@ Types # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match # [1, 2, 3] and [1, 'a'] operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] + import sys + print(operations, file=sys.stderr) _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 From d260ec0a9b7f2ee74fa864938757f58e6697abaf Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:38:39 -0700 Subject: [PATCH 047/124] Debug further --- doc/aerospike.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 2185118c22..a1b0574e01 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -162,6 +162,8 @@ Types operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] import sys print(operations, file=sys.stderr) + print(aerospike.OP_LIST_GET_BY_VALUE, file=sys.stderr) + print(type(aerospike.OP_LIST_GET_BY_VALUE), file=sys.stderr) _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 From cf83d664e9147ceca14791a4e1d30d906710b777 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:00:46 -0700 Subject: [PATCH 048/124] Add debug prints in glue code to see what type the client sees. TODO - prone to memory leaks --- src/main/client/operate.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index b743252b83..cce9f31ae6 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -1480,8 +1480,28 @@ static as_status get_operation(as_error *err, PyObject *op_dict, "Operation must contain an \"op\" entry"); } if (!PyLong_Check(py_operation)) { + PyObject *py_op_type = PyObject_Type(py_operation); + if (!py_op_type) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, + "Operation must be an integer, but got an " + "indeterminate type instead"); + } + PyObject *py_op_type_name = PyType_GetName(py_op_type); + if (!py_op_type_name) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, + "Operation must be an integer, but got an " + "indeterminate type instead"); + } + const char *op_type_name = PyUnicode_AsUTF8(py_op_type_name); + if (!op_type_name) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, + "Operation must be an integer, but got an " + "indeterminate type instead"); + } return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Operation must be an integer"); + "Operation must be an integer, but got a value " + "with type %s instead", + op_type_name); } *operation_ptr = PyLong_AsLong(py_operation); From a222bd88205b72e9a1915472613453e34fb4360d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:03:03 -0700 Subject: [PATCH 049/124] fix compiler error --- src/main/client/operate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index cce9f31ae6..80035936e6 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -1486,7 +1486,7 @@ static as_status get_operation(as_error *err, PyObject *op_dict, "Operation must be an integer, but got an " "indeterminate type instead"); } - PyObject *py_op_type_name = PyType_GetName(py_op_type); + PyObject *py_op_type_name = PyType_GetName((PyTypeObject *)py_op_type); if (!py_op_type_name) { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must be an integer, but got an " From 65eb2d51a830c9ccb3df5b88c7034afca37fa794 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:10:01 -0700 Subject: [PATCH 050/124] Make compatible with Python 3.10. PyType_GetName isn't available in the *compat.h header file --- src/main/client/operate.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 80035936e6..68c92dccf2 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -1486,13 +1486,7 @@ static as_status get_operation(as_error *err, PyObject *op_dict, "Operation must be an integer, but got an " "indeterminate type instead"); } - PyObject *py_op_type_name = PyType_GetName((PyTypeObject *)py_op_type); - if (!py_op_type_name) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Operation must be an integer, but got an " - "indeterminate type instead"); - } - const char *op_type_name = PyUnicode_AsUTF8(py_op_type_name); + const char *op_type_name = ((PyTypeObject *)py_op_type)->tp_name; if (!op_type_name) { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must be an integer, but got an " From 94f1a166b2abbe910f0aa6243e2e9c56ec6910e7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:15:21 -0700 Subject: [PATCH 051/124] Use better method --- src/main/client/operate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 68c92dccf2..c83daa99dd 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -1480,13 +1480,13 @@ static as_status get_operation(as_error *err, PyObject *op_dict, "Operation must contain an \"op\" entry"); } if (!PyLong_Check(py_operation)) { - PyObject *py_op_type = PyObject_Type(py_operation); + PyTypeObject *py_op_type = Py_TYPE(py_operation); if (!py_op_type) { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must be an integer, but got an " "indeterminate type instead"); } - const char *op_type_name = ((PyTypeObject *)py_op_type)->tp_name; + const char *op_type_name = py_op_type->tp_name; if (!op_type_name) { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must be an integer, but got an " From e184e7062af816eaaabfe67744d60a875fc9e7d9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 07:46:19 -0700 Subject: [PATCH 052/124] Printed out the type of the module constant, but try seeing what is actually stored in the operation --- doc/aerospike.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index a1b0574e01..aba9670491 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -161,9 +161,12 @@ Types # [1, 2, 3] and [1, 'a'] operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] import sys - print(operations, file=sys.stderr) print(aerospike.OP_LIST_GET_BY_VALUE, file=sys.stderr) print(type(aerospike.OP_LIST_GET_BY_VALUE), file=sys.stderr) + print(operations, file=sys.stderr) + print(operations[0]) + print(operations[0]['op']) + print(type(operations[0]['op'])) _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 From 26e0cf3ad9197c158dc97facb028ce399cc5cd5f Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 07:50:27 -0700 Subject: [PATCH 053/124] Forgot to print to stderr. doctest captures stdout --- doc/aerospike.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index aba9670491..65d2ce0c73 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -164,9 +164,9 @@ Types print(aerospike.OP_LIST_GET_BY_VALUE, file=sys.stderr) print(type(aerospike.OP_LIST_GET_BY_VALUE), file=sys.stderr) print(operations, file=sys.stderr) - print(operations[0]) - print(operations[0]['op']) - print(type(operations[0]['op'])) + print(operations[0], file=sys.stderr) + print(operations[0]['op'], file=sys.stderr) + print(type(operations[0]['op']), file=sys.stderr) _, _, bins = client.operate(key, operations) .. versionadded:: 3.5.0 From 21e30b68825e2a34466699898eff6583f563081a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:04:32 -0700 Subject: [PATCH 054/124] Try not mocking out aerospike to see if doctest extension depends on this setting to run aerospike_helper API methods --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index c603734228..fa05ddac4e 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,7 @@ # return MagicMock() -autodoc_mock_imports = ["aerospike"] +# autodoc_mock_imports = ["aerospike"] # sys.path.append(os.path.abspath('/usr/local/lib/python2.7/site-packages/aerospike-1.0.44-py2.7-macosx-10.9-x86_64.egg/')) From d319647846e8799a08e3099bd2f7ba9aecea185b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:27:41 -0700 Subject: [PATCH 055/124] Conditionally mock out aerospike if it isn't installed. This way doctest can run the code examples and aerospike_helpers doesn't use a mocked out aerospike when it does exist --- doc/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index fa05ddac4e..94c23ce51d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,10 @@ # return MagicMock() -# autodoc_mock_imports = ["aerospike"] +try: + import aerospike +except ImportError: + autodoc_mock_imports = ["aerospike"] # sys.path.append(os.path.abspath('/usr/local/lib/python2.7/site-packages/aerospike-1.0.44-py2.7-macosx-10.9-x86_64.egg/')) From 792b1b6948fd3ed928dec53bfeb1bdd1e4d38599 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:37:57 -0700 Subject: [PATCH 056/124] Fix 2 code examples in aerospike.rst. This assumes the elements are returned in list order --- doc/aerospike.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 65d2ce0c73..ceb2409b80 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -156,18 +156,16 @@ Types client = aerospike.client({'hosts': [('localhost', 3000)]}) key = 'test', 'demo', 1 - # get all values of the form [1, ] from a list of lists. - # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - # [1, 2, 3] and [1, 'a'] + client.put(key, bins={"list_bin": [[1, 2, 3], [2, 3, 4], [1, 'a']]}) + + # get all values of the form [1, ...] from a list of lists. operations = [list_ops.list_get_by_value('list_bin', [1, aerospike.CDTWildcard()], aerospike.LIST_RETURN_VALUE)] - import sys - print(aerospike.OP_LIST_GET_BY_VALUE, file=sys.stderr) - print(type(aerospike.OP_LIST_GET_BY_VALUE), file=sys.stderr) - print(operations, file=sys.stderr) - print(operations[0], file=sys.stderr) - print(operations[0]['op'], file=sys.stderr) - print(type(operations[0]['op']), file=sys.stderr) _, _, bins = client.operate(key, operations) + print(bins["list_bin"]) + + .. testoutput:: + + [[1, 2, 3], [1, 'a']] .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater @@ -187,11 +185,16 @@ Types client = aerospike.client({'hosts': [('localhost', 3000)]}) key = 'test', 'demo', 1 - # get all values of the form [1, ] from a list of lists. - # For example if list is [[1, 2, 3], [2, 3, 4], [1, 'a']], this operation will match - # [1, 2, 3] and [1, 'a'] + client.put(key, bins={"list_bin": [[1, 2, 3], [2, 3, 4], [1, 'a']]}) + + # get all values of the form [1, ...] from a list of lists. operations = [list_ops.list_get_by_value_range('list_bin', aerospike.LIST_RETURN_VALUE, [1], [1, aerospike.CDTInfinite()])] _, _, bins = client.operate(key, operations) + print(bins["list_bin"]) + + .. testoutput:: + + [[1, 2, 3], [1, 'a']] .. versionadded:: 3.5.0 .. note:: This requires Aerospike Server 4.3.1.3 or greater From f4e3b8422fbb5837c7b50ed487a00575f45d4625 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:43:00 -0700 Subject: [PATCH 057/124] Expand code example for hyperloglog --- aerospike_helpers/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index aa482023aa..d7a405a46e 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -20,9 +20,20 @@ class HyperLogLog(bytes): The constructor takes in any argument that the :class:`bytes` constructor takes in. - >>> from aerospike_helpers import HyperLogLog - >>> h = HyperLogLog([1, 2, 3]) - >>> client.put(key, {"hyperloglog": h}) + .. testcode:: + + from aerospike_helpers import HyperLogLog + h = HyperLogLog([1, 2, 3]) + + client = aerospike.client({'hosts': [('localhost', 3000)]}) + client.put(key, {"hyperloglog": h}) + + _, _, bins = client.get(key) + print(bins["hyperloglog"]) + + .. testoutput:: + + HyperLogLog(...) """ def __new__(cls, o) -> "HyperLogLog": return super().__new__(cls, o) From dca10b08daa554766ebc58e134a0443eeed93f59 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:50:58 -0700 Subject: [PATCH 058/124] Address a few more code examples failing. --- aerospike_helpers/batch/records.py | 15 +++++++++------ aerospike_helpers/cdt_ctx.py | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/aerospike_helpers/batch/records.py b/aerospike_helpers/batch/records.py index c4bf02810b..78e665fcc6 100644 --- a/aerospike_helpers/batch/records.py +++ b/aerospike_helpers/batch/records.py @@ -339,12 +339,15 @@ def __init__(self, batch_records: Optional[TypeBatchRecordList] = None) -> None: for br in brs.batch_records: print(br.result) print(br.record) - # 0 - # (('test', 'demo', 1, bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) - # 0 - # (('test', 'demo', 2, bytearray(b'...')), {'ttl': 2592000, 'gen': 4}, {'id': 100}) - # 0 - # (('test', 'demo', 3, bytearray(b'...')), {'ttl': 2592000, 'gen': 3}, {'id': 1}) + + .. testoutput:: + + 0 + (('test', 'demo', 1, bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) + 0 + (('test', 'demo', 2, bytearray(b'...')), {'ttl': 2592000, 'gen': 4}, {'id': 100}) + 0 + (('test', 'demo', 3, bytearray(b'...')), {'ttl': 2592000, 'gen': 3}, {'id': 1}) """ if batch_records is None: diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index f8d30eb84d..8449244e72 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -83,6 +83,7 @@ # Example 3: create a CDT secondary index from a base64 encoded _cdt_ctx with info command policy = {} + ctx_list_index = [cdt_ctx.cdt_ctx_list_index(0)] bs_b4_cdt = client.get_cdtctx_base64(ctx_list_index) r = [] From 46b4a20a060a146d8f9687efe41f305386d7bad0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:03:07 -0700 Subject: [PATCH 059/124] Address more code example failures. Use ellipsis for nonexact matches --- .../operations/bitwise_operations.py | 18 ++++++++++++------ .../operations/expression_operations.py | 12 ++++++++---- aerospike_helpers/operations/hll_operations.py | 18 ++++++++++++------ doc/client.rst | 12 ++++++------ 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/aerospike_helpers/operations/bitwise_operations.py b/aerospike_helpers/operations/bitwise_operations.py index 67b48c1b9e..364ed724ad 100644 --- a/aerospike_helpers/operations/bitwise_operations.py +++ b/aerospike_helpers/operations/bitwise_operations.py @@ -54,13 +54,11 @@ _, _, bins = client.get(key) print("5 bytes: ", bins) - # 5 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01'} _, _, _ = client.operate(key, ops) _, _, newbins = client.get(key) print("After resize to 10 bytes: ", newbins) - # After resize to 10 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00'} # EXAMPLE 2: shrink the five_ones bin to a bytesize of 5 from the front. @@ -73,12 +71,17 @@ _, _, _ = client.operate(key, ops) _, _, newbins = client.get(key) print("After resize to 5 bytes again: ", newbins) - # After resize to 5 bytes again: {'bitwise1': b'\x00\x00\x00\x00\x00'} # Cleanup and close the connection to the Aerospike cluster. client.remove(key) client.close() + .. testoutput:: + + 5 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01'} + After resize to 10 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00'} + After resize to 5 bytes again: {'bitwise1': b'\x00\x00\x00\x00\x00'} + Example: .. testcode:: @@ -110,7 +113,6 @@ ] _, _, results = client.operate(key, ops) print(results) - # {'bitwise1': b'\x01\x01\x01\x01\x01'} # Example 2: modify bits using the 'or' op, then read bits # 0 = offset @@ -123,7 +125,6 @@ ] _, _, results = client.operate(key, ops) print(results) - # {'bitwise1': b'\xff\x01\x01\x01\x01'} # Example 3: modify bits using the 'remove' op, then read bits' # offset = 0 @@ -135,10 +136,15 @@ ] _, _, results = client.operate(key, ops) print(results) - # {'bitwise1': b'\x01\x01\x01'} client.close() + .. testoutput:: + + {'bitwise1': b'\x01\x01\x01\x01\x01'} + {'bitwise1': b'\xff\x01\x01\x01\x01'} + {'bitwise1': b'\x01\x01\x01'} + .. seealso:: `Bits (Data Types) `_. """ import aerospike diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index 0573006bf7..eed98b0f28 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -54,7 +54,6 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r # Read the value of int bin "balance". # Let 'client' be a connected aerospike client. # Let int bin 'balance' == 50. - from aerospike_helpers.operations import expression_operations as expressions from aerospike_helpers.expressions import * @@ -65,7 +64,10 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r _, _, res = client.operate(self.key, ops) print(res) - # EXPECTED OUTPUT: {"balance": 50} + .. testoutput:: + + {"balance": 50} + """ op_dict = { @@ -98,7 +100,6 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ # Write the value of int bin "balance" + 50 back to "balance". # Let 'client' be a connected aerospike client. # Let int bin 'balance' == 50. - from aerospike_helpers.operations import expression_operations as expr_ops from aerospike_helpers.expressions import * @@ -110,7 +111,10 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ _, _, res = client.get(self.key) print(res) - # EXPECTED OUTPUT: {"balance": 100} + .. testoutput:: + + {"balance": 100} + """ op_dict = { diff --git a/aerospike_helpers/operations/hll_operations.py b/aerospike_helpers/operations/hll_operations.py index 73ea55de5f..40ebcd804b 100644 --- a/aerospike_helpers/operations/hll_operations.py +++ b/aerospike_helpers/operations/hll_operations.py @@ -80,24 +80,30 @@ # Pass in Amy's key _, _, res = client.operate(keys[0], ops) print("Estimated items viewed intersection:", res["viewed"]) - # Estimated items viewed intersection: 251 - # Actual intersection: 250 + print("Actual intersection: 250") # Find out how many unique products Amy, Farnsworth, and Scruffy have viewed. ops = [hll_ops.hll_get_union_count("viewed", viewed)] _, _, res = client.operate(keys[0], ops) print("Estimated items viewed union:", res["viewed"]) - # Estimated items viewed union: 1010 - # Actual union: 1000 + print("Actual union: 1000") # Find the similarity of Amy, Farnsworth, and Scruffy's product views. ops = [hll_ops.hll_get_similarity("viewed", viewed)] _, _, res = client.operate(keys[0], ops) print("Estimated items viewed similarity: %f%%" % (res["viewed"] * 100)) - # Estimated items viewed similarity: 24.888393% - # Actual similarity: 25% + print("Actual similarity: 25%") + +.. testoutput:: + + Estimated items viewed intersection: 251 + Actual intersection: 250 + Estimated items viewed union: 1010 + Actual union: 1000 + Estimated items viewed similarity: 24.888393% + Actual similarity: 25% """ diff --git a/doc/client.rst b/doc/client.rst index e7b5b75d0c..10384c12de 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -489,7 +489,7 @@ String Operations .. testoutput:: - {"bin1": "Gordon Freeman"} + {'bin1': 'Gordon Freeman'} .. index:: single: Numeric Operations @@ -523,12 +523,12 @@ Numeric Operations # Gain health client.increment(keyTuple, 'lives', 10) (key, meta, bins) = client.get(keyTuple) - print("Lives:", bins) + print("Lives:", bins["lives"]) # Take damage client.increment(keyTuple, 'lives', -90) (key, meta, bins) = client.get(keyTuple) - print("Lives:", bins) + print("Lives:", bins["lives"]) .. testoutput:: @@ -766,7 +766,7 @@ Info Operations .. testoutput:: - [{'address': '1.1.1.1', 'port': 3000, 'node_name': 'BCER199932C'}, {'address': '1.1.1.1', 'port': 3010, 'node_name': 'ADFFE7782CD'}] + [{'address': '...', 'port': ..., 'node_name': '...'}...] .. versionchanged:: 6.0.0 @@ -785,7 +785,7 @@ Info Operations .. testoutput:: - [('127.0.0.1', 3000), ('127.0.0.1', 3010)] + [('127.0.0.1', 3000)...] .. versionchanged:: 3.0.0 @@ -821,7 +821,7 @@ Info Operations .. testoutput:: - {'BB9020011AC4202': (None, 'test\n')} + {'...': (None, 'test\n')} .. versionadded:: 3.0.0 From 2477217eebc3e14de840e823f1f182957c7bd9f2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:11:17 -0700 Subject: [PATCH 060/124] Fix udf examples --- doc/client.rst | 63 +++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 10384c12de..3c6d77c094 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -591,6 +591,35 @@ Transactions User Defined Functions ---------------------- +.. testcode:: udf + + import aerospike + + config = { + 'hosts': [ ('127.0.0.1', 3000)], + 'lua': { 'user_path': '~/lua-scripts/'} + } + client = aerospike.client(config) + # Register the UDF module and copy it to the Lua 'user_path' + client.udf_put('doc/examples/scan/my_udf.lua') + + print("Before remove:", client.udf_list()) + + client.udf_remove('my_udf.lua') + print("After remove:", client.udf_list()) + + client.close() + +.. testoutput:: + + [ + {'content': bytearray(b'...'), + 'hash': bytearray(b'...'), + 'name': 'my_udf.lua', + 'type': 0} + ] + [] + .. class:: Client :noindex: @@ -612,19 +641,6 @@ User Defined Functions .. :emphasize-lines: 5,9 - .. testcode:: - - import aerospike - - config = { - 'hosts': [ ('127.0.0.1', 3000)], - 'lua': { 'user_path': '/path/to/lua/user_path'} - } - client = aerospike.client(config) - # Register the UDF module and copy it to the Lua 'user_path' - client.udf_put('/path/to/my_module.lua') - client.close() - .. method:: udf_remove(module[, policy: dict]) Remove a previously registered UDF module from the cluster. @@ -635,10 +651,6 @@ User Defined Functions :param dict policy: currently **timeout** in milliseconds is the available policy. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. testcode:: - - client.udf_remove('my_module.lua') - .. method:: udf_list([policy: dict]) -> [] Return the list of UDF modules registered with the cluster. @@ -647,23 +659,6 @@ User Defined Functions :rtype: :class:`list` :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. testcode:: - - print(client.udf_list()) - - .. testoutput:: - - [ - {'content': bytearray(b''), - 'hash': bytearray(b'195e39ceb51c110950bd'), - 'name': 'my_udf1.lua', - 'type': 0}, - {'content': bytearray(b''), - 'hash': bytearray(b'8a2528e8475271877b3b'), - 'name': 'stream_udf.lua', - 'type': 0} - ] - .. method:: udf_get(module: str[, language: int = aerospike.UDF_TYPE_LUA[, policy: dict]]) -> str Return the content of a UDF module which is registered with the cluster. From 5bb7d3f48750318c25b187a377852c02661264ad Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:15:03 -0700 Subject: [PATCH 061/124] Add missing import --- aerospike_helpers/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index d7a405a46e..05274bb325 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -23,6 +23,8 @@ class HyperLogLog(bytes): .. testcode:: from aerospike_helpers import HyperLogLog + import aerospike + h = HyperLogLog([1, 2, 3]) client = aerospike.client({'hosts': [('localhost', 3000)]}) From 3145ba71f6bd72cb1d8af56ddb8b68fe7aa1327e Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:17:42 -0700 Subject: [PATCH 062/124] Make sure udf output matches code example --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index 3c6d77c094..215b454d5f 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -610,7 +610,7 @@ User Defined Functions client.close() -.. testoutput:: +.. testoutput:: udf [ {'content': bytearray(b'...'), From fa2a5f68087804b8235b0e3d0b2b16b07e2fcf44 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:22:33 -0700 Subject: [PATCH 063/124] Fix indentation --- .../operations/bitwise_operations.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/aerospike_helpers/operations/bitwise_operations.py b/aerospike_helpers/operations/bitwise_operations.py index 364ed724ad..7f234bd916 100644 --- a/aerospike_helpers/operations/bitwise_operations.py +++ b/aerospike_helpers/operations/bitwise_operations.py @@ -76,11 +76,11 @@ client.remove(key) client.close() - .. testoutput:: +.. testoutput:: - 5 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01'} - After resize to 10 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00'} - After resize to 5 bytes again: {'bitwise1': b'\x00\x00\x00\x00\x00'} + 5 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01'} + After resize to 10 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00'} + After resize to 5 bytes again: {'bitwise1': b'\x00\x00\x00\x00\x00'} Example: @@ -139,11 +139,11 @@ client.close() - .. testoutput:: +.. testoutput:: - {'bitwise1': b'\x01\x01\x01\x01\x01'} - {'bitwise1': b'\xff\x01\x01\x01\x01'} - {'bitwise1': b'\x01\x01\x01'} + {'bitwise1': b'\x01\x01\x01\x01\x01'} + {'bitwise1': b'\xff\x01\x01\x01\x01'} + {'bitwise1': b'\x01\x01\x01'} .. seealso:: `Bits (Data Types) `_. """ From 45f8580ef10c2c8c64ac1fffdd47a8052283b798 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:24:31 -0700 Subject: [PATCH 064/124] Add missing key --- aerospike_helpers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 05274bb325..133e2e0229 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -28,6 +28,7 @@ class HyperLogLog(bytes): h = HyperLogLog([1, 2, 3]) client = aerospike.client({'hosts': [('localhost', 3000)]}) + key = ("test", "demo", 1) client.put(key, {"hyperloglog": h}) _, _, bins = client.get(key) From e5b0d3a8b7d3f7e9336a02411eead1d36581da2a Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:26:26 -0700 Subject: [PATCH 065/124] Attempt to fix udf code example error --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index 215b454d5f..c5f8bfecf2 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -601,7 +601,7 @@ User Defined Functions } client = aerospike.client(config) # Register the UDF module and copy it to the Lua 'user_path' - client.udf_put('doc/examples/scan/my_udf.lua') + client.udf_put('./examples/scan/my_udf.lua') print("Before remove:", client.udf_list()) From ef1a6b18545606267ecb7109cdba099c1d4a79df Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:29:31 -0700 Subject: [PATCH 066/124] This example doesn't explicitly test ttl/gen so we can just ignore it --- aerospike_helpers/batch/records.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/batch/records.py b/aerospike_helpers/batch/records.py index 78e665fcc6..5bd9fd3696 100644 --- a/aerospike_helpers/batch/records.py +++ b/aerospike_helpers/batch/records.py @@ -343,11 +343,11 @@ def __init__(self, batch_records: Optional[TypeBatchRecordList] = None) -> None: .. testoutput:: 0 - (('test', 'demo', 1, bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) + (('test', 'demo', 1, bytearray(b'...')), {...}, {}) 0 - (('test', 'demo', 2, bytearray(b'...')), {'ttl': 2592000, 'gen': 4}, {'id': 100}) + (('test', 'demo', 2, bytearray(b'...')), {...}, {'id': 100}) 0 - (('test', 'demo', 3, bytearray(b'...')), {'ttl': 2592000, 'gen': 3}, {'id': 1}) + (('test', 'demo', 3, bytearray(b'...')), {...}, {'id': 1}) """ if batch_records is None: From 3a0a1aebc25fc054732876c9a16e7c93227ac6a2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:05:39 -0700 Subject: [PATCH 067/124] Address one test failure --- aerospike_helpers/expressions/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index 3527976451..dc70139333 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1055,14 +1055,17 @@ def __init__(self, *exprs: _BaseExpr): ] record = client.operate(keyTuple, ops) print(record) - # (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'results': 70}) client.put(keyTuple, {"operation": "divide"}) record = client.operate(keyTuple, ops) print(record) # Divide isn't supported, so we get -1 - # (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'results': -1}) + + .. testoutput:: + (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'results': 70}) + (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'results': -1}) + """ self._children = exprs + (_GenericExpr(_ExprOp._AS_EXP_CODE_END_OF_VA_ARGS, 0, {}),) From 5ec69d2d2194cebc99913bc8b3663f79a49bcb36 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 1 May 2026 16:06:18 -0700 Subject: [PATCH 068/124] Address a few errors --- aerospike_helpers/expressions/base.py | 1 + doc/client.rst | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/aerospike_helpers/expressions/base.py b/aerospike_helpers/expressions/base.py index dc70139333..e263e48faa 100644 --- a/aerospike_helpers/expressions/base.py +++ b/aerospike_helpers/expressions/base.py @@ -1063,6 +1063,7 @@ def __init__(self, *exprs: _BaseExpr): # Divide isn't supported, so we get -1 .. testoutput:: + (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'results': 70}) (('test', 'demo', 'key', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'results': -1}) diff --git a/doc/client.rst b/doc/client.rst index c5f8bfecf2..b8513e4859 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -612,13 +612,8 @@ User Defined Functions .. testoutput:: udf - [ - {'content': bytearray(b'...'), - 'hash': bytearray(b'...'), - 'name': 'my_udf.lua', - 'type': 0} - ] - [] + Before remove: [{'content': bytearray(b'...'), 'hash': bytearray(b'...'), 'name': 'my_udf.lua', 'type': 0}] + After remove: [] .. class:: Client :noindex: From 8d9675aa51e4bf149515d33dead302369b4765de Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 1 May 2026 16:10:35 -0700 Subject: [PATCH 069/124] Address a few errors --- aerospike_helpers/cdt_ctx.py | 7 +++++-- aerospike_helpers/operations/bitwise_operations.py | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/aerospike_helpers/cdt_ctx.py b/aerospike_helpers/cdt_ctx.py index 8449244e72..d886c5fd29 100644 --- a/aerospike_helpers/cdt_ctx.py +++ b/aerospike_helpers/cdt_ctx.py @@ -53,7 +53,6 @@ _, _, result = client.operate(key, ops) print(result) - # {'users': 200} # Example 2: add a new person and get their rating of Facebook cindy = { @@ -78,7 +77,6 @@ _, _, result = client.operate(key, ops) print(result) - # {'users': 4} # Example 3: create a CDT secondary index from a base64 encoded _cdt_ctx with info command policy = {} @@ -104,6 +102,11 @@ client.remove(key) client.close() +.. testoutput:: + + {'users': 200} + {'users': 4} + .. _path_expressions_contexts: Path Expressions Contexts diff --git a/aerospike_helpers/operations/bitwise_operations.py b/aerospike_helpers/operations/bitwise_operations.py index 7f234bd916..a4182a338d 100644 --- a/aerospike_helpers/operations/bitwise_operations.py +++ b/aerospike_helpers/operations/bitwise_operations.py @@ -78,9 +78,9 @@ .. testoutput:: - 5 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01'} - After resize to 10 bytes: {'bitwise1': b'\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00'} - After resize to 5 bytes again: {'bitwise1': b'\x00\x00\x00\x00\x00'} + 5 bytes: {'bitwise1': b'\\x01\\x01\\x01\\x01\\x01'} + After resize to 10 bytes: {'bitwise1': b'\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00'} + After resize to 5 bytes again: {'bitwise1': b'\\x00\\x00\\x00\\x00\\x00'} Example: @@ -141,9 +141,9 @@ .. testoutput:: - {'bitwise1': b'\x01\x01\x01\x01\x01'} - {'bitwise1': b'\xff\x01\x01\x01\x01'} - {'bitwise1': b'\x01\x01\x01'} + {'bitwise1': b'\\x01\\x01\\x01\\x01\\x01'} + {'bitwise1': b'\\xff\\x01\\x01\\x01\\x01'} + {'bitwise1': b'\\x01\\x01\\x01'} .. seealso:: `Bits (Data Types) `_. """ From 6d295c9128f734386ecd9bf8d57493f5d9b7fd7b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 1 May 2026 16:13:39 -0700 Subject: [PATCH 070/124] Getting import errors for expr op examples. Not sure why this is happening so try moving imports to the top --- aerospike_helpers/operations/expression_operations.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index eed98b0f28..ae4b55555f 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -51,12 +51,12 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r .. testcode:: - # Read the value of int bin "balance". - # Let 'client' be a connected aerospike client. - # Let int bin 'balance' == 50. from aerospike_helpers.operations import expression_operations as expressions from aerospike_helpers.expressions import * + # Read the value of int bin "balance". + # Let 'client' be a connected aerospike client. + # Let int bin 'balance' == 50. expr = IntBin("balance").compile() ops = [ expressions.expression_read("balance", expr) @@ -97,11 +97,12 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ .. testcode:: + from aerospike_helpers.operations import expression_operations as expr_ops + from aerospike_helpers.expressions import * + # Write the value of int bin "balance" + 50 back to "balance". # Let 'client' be a connected aerospike client. # Let int bin 'balance' == 50. - from aerospike_helpers.operations import expression_operations as expr_ops - from aerospike_helpers.expressions import * expr = Add(IntBin("balance"), 50).compile() ops = [ From 23e1d2a74bc984e3cf7258971cafd2e4f8cc3bb5 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 1 May 2026 16:20:02 -0700 Subject: [PATCH 071/124] Show logs to see why HyperLogLog code example fails --- .github/workflows/smoke-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index e8760c9901..641fe87303 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -429,6 +429,9 @@ jobs: run: sphinx-build -b doctest . doctest working-directory: doc + - if: ${{ !cancelled() }} + run: docker logs aerospike + test-ee: runs-on: ubuntu-22.04 needs: build From e36bc42d19b870fb0cfee52419a4cdad042c5112 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 1 May 2026 16:46:25 -0700 Subject: [PATCH 072/124] Add scaffolding for hll code examples TODO not done --- aerospike_helpers/expressions/hll.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index bcb364573d..a2c7ab9552 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -42,6 +42,18 @@ class HLLInit(_BaseExpr): If 1 of index_bit_count or mh_bit_count are set, an existing HLL bin will set that config and retain its current value for the unset config. If the HLL bin does not exist, index_bit_count is required to create it, mh_bit_count is optional. + + .. testsetup:: + + import aerospike + client = aerospike.client({'hosts': [('localhost', 3000)]}) + ops = [ + expr = exp.HLLInit(None, 12, 24, exp.HLLBin("d")) + ] + key = ("test", "demo", 1) + client.operate(key, ops) + + values = [] """ _op = aerospike.OP_HLL_INIT From 047f82e65c65a6e3daf7fae62d6047d37d33f419 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 7 May 2026 17:07:31 -0700 Subject: [PATCH 073/124] Fix code example by creating hll object in server instead of in code example... --- aerospike_helpers/__init__.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 133e2e0229..806ece989e 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -16,27 +16,27 @@ class HyperLogLog(bytes): """ - Represents a HyperLogLog value. This can be returned from the server or created in order to be sent to the server. - - The constructor takes in any argument that the :class:`bytes` constructor takes in. + Represents a HyperLogLog value. This can be returned from or sent to the server. .. testcode:: + from aerospike_helpers.operations import hll_operations - from aerospike_helpers import HyperLogLog - import aerospike - - h = HyperLogLog([1, 2, 3]) + BIN_NAME="hll" + ops = [ + hll_operations.hll_init(BIN_NAME, index_bit_count=4, mh_bit_count=4) + ] + client.operate(keyTuple, ops) + _, _, bins = client.get(keyTuple) + print(bins[BIN_NAME]) - client = aerospike.client({'hosts': [('localhost', 3000)]}) - key = ("test", "demo", 1) - client.put(key, {"hyperloglog": h}) - - _, _, bins = client.get(key) - print(bins["hyperloglog"]) + client.put(keyTuple, bins) + _, _, bins = client.get(keyTuple) + print(bins[BIN_NAME]) .. testoutput:: HyperLogLog(...) + HyperLogLog(...) """ def __new__(cls, o) -> "HyperLogLog": return super().__new__(cls, o) From 86daa850b10f8e626868a04915bb0fe2c7bac286 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 7 May 2026 19:18:20 -0700 Subject: [PATCH 074/124] Fix indenting --- aerospike_helpers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 806ece989e..77d3cee734 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -19,6 +19,7 @@ class HyperLogLog(bytes): Represents a HyperLogLog value. This can be returned from or sent to the server. .. testcode:: + from aerospike_helpers.operations import hll_operations BIN_NAME="hll" From 4676596e8b1f0d15e14de2ab8c4da2dbf832090e Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 7 May 2026 19:22:21 -0700 Subject: [PATCH 075/124] Address failing test case --- aerospike_helpers/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 77d3cee734..05f1b412d2 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -21,6 +21,9 @@ class HyperLogLog(bytes): .. testcode:: from aerospike_helpers.operations import hll_operations + import aerospike + + client = aerospike.client({'hosts': [('localhost', 3000)]}) BIN_NAME="hll" ops = [ From 0e11e733437e509fcc2bb7cfd54355fc8e339eda Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 7 May 2026 19:26:11 -0700 Subject: [PATCH 076/124] Address failing test case --- aerospike_helpers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py index 05f1b412d2..731c3c286e 100644 --- a/aerospike_helpers/__init__.py +++ b/aerospike_helpers/__init__.py @@ -29,6 +29,7 @@ class HyperLogLog(bytes): ops = [ hll_operations.hll_init(BIN_NAME, index_bit_count=4, mh_bit_count=4) ] + keyTuple = ("test", "demo", 1) client.operate(keyTuple, ops) _, _, bins = client.get(keyTuple) print(bins[BIN_NAME]) From ae9e462bd7f281b746811289b106c51796846575 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 7 May 2026 19:31:37 -0700 Subject: [PATCH 077/124] Fix bad syntax in example --- aerospike_helpers/expressions/hll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index a2c7ab9552..327d653d62 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -48,7 +48,7 @@ class HLLInit(_BaseExpr): import aerospike client = aerospike.client({'hosts': [('localhost', 3000)]}) ops = [ - expr = exp.HLLInit(None, 12, 24, exp.HLLBin("d")) + exp.HLLInit(None, 12, 24, exp.HLLBin("d")) ] key = ("test", "demo", 1) client.operate(key, ops) From 7966d2c3f14c0d131c95aa623d1c06999f4ded95 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 8 May 2026 07:57:09 -0700 Subject: [PATCH 078/124] Just noticed this is a duplicate code example for the same class --- aerospike_helpers/expressions/hll.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 327d653d62..bcb364573d 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -42,18 +42,6 @@ class HLLInit(_BaseExpr): If 1 of index_bit_count or mh_bit_count are set, an existing HLL bin will set that config and retain its current value for the unset config. If the HLL bin does not exist, index_bit_count is required to create it, mh_bit_count is optional. - - .. testsetup:: - - import aerospike - client = aerospike.client({'hosts': [('localhost', 3000)]}) - ops = [ - exp.HLLInit(None, 12, 24, exp.HLLBin("d")) - ] - key = ("test", "demo", 1) - client.operate(key, ops) - - values = [] """ _op = aerospike.OP_HLL_INIT From f7673264f8696e08938c50ae28cce89bf9bfe697 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 8 May 2026 08:15:51 -0700 Subject: [PATCH 079/124] Revise one code example to make more clear how this works --- aerospike_helpers/expressions/hll.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index bcb364573d..b9247e94c6 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -17,6 +17,14 @@ HyperLogLog expressions contain expressions for performing HLL operations. Most of these operations are equivalent to the :mod:`HyperLogLog API `. +# TODO: Run once for entire file +.. testsetup:: + + from aerospike_helpers.operations import hll_operations + + config = {"hosts": [("127.0.0.1", 3000)]} + client = aerospike.client(config) + key = ("test", "demo", 1) """ # from __future__ import annotations @@ -103,9 +111,27 @@ def __init__( .. testcode:: # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("d", ['key1', 'key2', 'key3']) + ] + client.operate(key, ops) + + from aerospike_helpers.operations import expression_operations + from aerospike_helpers.operations import operations # Add ['key4', 'key5', 'key6'] so that the returned value is ['key1', 'key2', 'key3', 'key4', 'key5', # 'key6'] expr = exp.HLLAdd(None, ['key4', 'key5', 'key6'], 8, 8, exp.HLLBin("d")).compile() + ops = [ + expression_operations.expression_write("d", expr) + operations.read("d") + ] + _, _, bins = client.operate(key, ops) + print(bins["d"]) + + .. testoutput:: + + HyperLogLog(...) """ self._children = ( list, From cbe173efe5d388f1b787bbeaee4eeff660fc9825 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 8 May 2026 08:38:23 -0700 Subject: [PATCH 080/124] Fix syntax error --- aerospike_helpers/expressions/hll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index b9247e94c6..ccad302018 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -123,7 +123,7 @@ def __init__( # 'key6'] expr = exp.HLLAdd(None, ['key4', 'key5', 'key6'], 8, 8, exp.HLLBin("d")).compile() ops = [ - expression_operations.expression_write("d", expr) + expression_operations.expression_write("d", expr), operations.read("d") ] _, _, bins = client.operate(key, ops) From 25821d1fca639dc3b19c03d9b2d1182d7393ceab Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 09:32:14 -0700 Subject: [PATCH 081/124] Convert remaining python code examples --- doc/aerospike_helpers.expressions.rst | 4 +++- doc/client.rst | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/aerospike_helpers.expressions.rst b/doc/aerospike_helpers.expressions.rst index b32f11690f..fcdc4f165f 100644 --- a/doc/aerospike_helpers.expressions.rst +++ b/doc/aerospike_helpers.expressions.rst @@ -23,7 +23,9 @@ such as :meth:`~aerospike_helpers.expressions.base.Eq` or :meth:`~aerospike_help while passing them other expressions and constants as arguments, and finally calling the :meth:`~aerospike_helpers.expressions.resources._BaseExpr.compile` method. -Example:: +Example: + +.. testcode:: # See if integer bin "bin_name" contains a value equal to 10. from aerospike_helpers import expressions as exp diff --git a/doc/client.rst b/doc/client.rst index b8513e4859..cbb41215a6 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -2491,7 +2491,7 @@ Privilege Objects If not specified, the privilege applies to the entire namespace. - Example:: + .. testcode:: {'code': aerospike.PRIV_READ, 'ns': 'test', 'set': 'demo'} From 58fb349f79714ceeec36ad2acf894d81e1ce3428 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 09:38:21 -0700 Subject: [PATCH 082/124] Address doctest failure by checking dict in the order the keys are returned by the client --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index cbb41215a6..c29b7d2dbf 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -612,7 +612,7 @@ User Defined Functions .. testoutput:: udf - Before remove: [{'content': bytearray(b'...'), 'hash': bytearray(b'...'), 'name': 'my_udf.lua', 'type': 0}] + Before remove: [{'name': 'my_udf.lua', 'hash': bytearray(b'...'), 'type': 0, 'content': bytearray(b'...')}] After remove: [] .. class:: Client From 7d6ad8e5a1e0466a0035108803eee6a3df03a1a4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 09:49:26 -0700 Subject: [PATCH 083/124] Address indent error in code examples --- .../operations/expression_operations.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index ae4b55555f..86dff56441 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -49,24 +49,24 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r Example: - .. testcode:: + .. testcode:: - from aerospike_helpers.operations import expression_operations as expressions - from aerospike_helpers.expressions import * + from aerospike_helpers.operations import expression_operations as expressions + from aerospike_helpers.expressions import * - # Read the value of int bin "balance". - # Let 'client' be a connected aerospike client. - # Let int bin 'balance' == 50. - expr = IntBin("balance").compile() - ops = [ - expressions.expression_read("balance", expr) - ] - _, _, res = client.operate(self.key, ops) - print(res) + # Read the value of int bin "balance". + # Let 'client' be a connected aerospike client. + # Let int bin 'balance' == 50. + expr = IntBin("balance").compile() + ops = [ + expressions.expression_read("balance", expr) + ] + _, _, res = client.operate(self.key, ops) + print(res) - .. testoutput:: + .. testoutput:: - {"balance": 50} + {"balance": 50} """ @@ -95,26 +95,26 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ Example: - .. testcode:: + .. testcode:: - from aerospike_helpers.operations import expression_operations as expr_ops - from aerospike_helpers.expressions import * + from aerospike_helpers.operations import expression_operations as expr_ops + from aerospike_helpers.expressions import * - # Write the value of int bin "balance" + 50 back to "balance". - # Let 'client' be a connected aerospike client. - # Let int bin 'balance' == 50. + # Write the value of int bin "balance" + 50 back to "balance". + # Let 'client' be a connected aerospike client. + # Let int bin 'balance' == 50. - expr = Add(IntBin("balance"), 50).compile() - ops = [ - expr_ops.expression_write("balance", expr) - ] - client.operate(self.key, ops) - _, _, res = client.get(self.key) - print(res) + expr = Add(IntBin("balance"), 50).compile() + ops = [ + expr_ops.expression_write("balance", expr) + ] + client.operate(self.key, ops) + _, _, res = client.get(self.key) + print(res) - .. testoutput:: + .. testoutput:: - {"balance": 100} + {"balance": 100} """ From e4d0a554e9f33528e053d33e225e2ba616a421df Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 09:55:56 -0700 Subject: [PATCH 084/124] Address doctest failure --- aerospike_helpers/operations/expression_operations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index 86dff56441..c23867aeea 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -61,7 +61,7 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r ops = [ expressions.expression_read("balance", expr) ] - _, _, res = client.operate(self.key, ops) + _, _, res = client.operate(key, ops) print(res) .. testoutput:: @@ -108,9 +108,9 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ ops = [ expr_ops.expression_write("balance", expr) ] - client.operate(self.key, ops) + client.operate(key, ops) _, _, res = client.get(self.key) - print(res) + print(res) .. testoutput:: From d1effb901d89a19d8fc1f54257bd789f36e9f74e Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 10:01:39 -0700 Subject: [PATCH 085/124] Fix formatting --- aerospike_helpers/operations/expression_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index c23867aeea..8b5cb47f7f 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -66,7 +66,7 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r .. testoutput:: - {"balance": 50} + {"balance": 50} """ From e3ab81c68a3239bacff1ac7ddcf40e8879887673 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 10:04:15 -0700 Subject: [PATCH 086/124] Address doctest failures --- .../operations/expression_operations.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index 8b5cb47f7f..be5d128fe0 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -56,13 +56,14 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r # Read the value of int bin "balance". # Let 'client' be a connected aerospike client. - # Let int bin 'balance' == 50. + client.put(key, bins={'balance': 50}) + expr = IntBin("balance").compile() ops = [ expressions.expression_read("balance", expr) ] - _, _, res = client.operate(key, ops) - print(res) + _, _, bins = client.operate(key, ops) + print(bins) .. testoutput:: @@ -102,15 +103,15 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ # Write the value of int bin "balance" + 50 back to "balance". # Let 'client' be a connected aerospike client. - # Let int bin 'balance' == 50. + client.put(key, bins={'balance': 50}) expr = Add(IntBin("balance"), 50).compile() ops = [ expr_ops.expression_write("balance", expr) ] client.operate(key, ops) - _, _, res = client.get(self.key) - print(res) + _, _, bins = client.get(self.key) + print(bins) .. testoutput:: From 552305f7f8ff497b5a3001fb9c0f3dc6170353ba Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 10:18:50 -0700 Subject: [PATCH 087/124] Address test failure. Assuming doctest only returns strs as single quotes --- aerospike_helpers/operations/expression_operations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index be5d128fe0..1f046bb5d7 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -67,7 +67,7 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r .. testoutput:: - {"balance": 50} + {'balance': 50} """ @@ -110,12 +110,12 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ expr_ops.expression_write("balance", expr) ] client.operate(key, ops) - _, _, bins = client.get(self.key) + _, _, bins = client.get(key) print(bins) .. testoutput:: - {"balance": 100} + {'balance': 100} """ From 570c7710cb6e2e92a2b24a4f6a99c85aaad16528 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 10:24:56 -0700 Subject: [PATCH 088/124] Addr test failures --- aerospike_helpers/operations/expression_operations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aerospike_helpers/operations/expression_operations.py b/aerospike_helpers/operations/expression_operations.py index 1f046bb5d7..b9f6ca218c 100644 --- a/aerospike_helpers/operations/expression_operations.py +++ b/aerospike_helpers/operations/expression_operations.py @@ -63,11 +63,11 @@ def expression_read(bin_name: str, expression: resources._BaseExpr, expression_r expressions.expression_read("balance", expr) ] _, _, bins = client.operate(key, ops) - print(bins) + print(bins['balance']) .. testoutput:: - {'balance': 50} + 50 """ @@ -111,11 +111,11 @@ def expression_write(bin_name: str, expression: resources._BaseExpr, expression_ ] client.operate(key, ops) _, _, bins = client.get(key) - print(bins) + print(bins['balance']) .. testoutput:: - {'balance': 100} + 100 """ From 70229912fe3cf3f803440c8a253d6860f3478d20 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 11:55:45 -0700 Subject: [PATCH 089/124] Add missing import --- aerospike_helpers/expressions/hll.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index ccad302018..0d037f41af 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -110,6 +110,8 @@ def __init__( .. testcode:: + from aerospike_helpers.operations import hll_operations + # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. ops = [ hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), From 50103a37372d62fd09e1095ba734f2d5c02f8178 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 12:04:22 -0700 Subject: [PATCH 090/124] Fill in missing boilerplate code for HLL examples. --- aerospike_helpers/expressions/hll.py | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 0d037f41af..7c18248ba9 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -187,6 +187,17 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): .. testcode:: # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("d", ['key%s' % str(i) for i in range(10000)]), + hll_operations.hll_init("e", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("e", ['key%s' % str(i) for i in range(5000, 15000)]) + ] + client.operate(key, ops) + + _, _, bins = client.get(key) + values = [bins["e"]] + # Let values be a list containing HLL objects retrieved from the aerospike database. # Find the union of HLL bin "d" and all HLLs in values. expr = exp.HLLGetUnion(values, exp.HLLBin("d")).compile() @@ -215,6 +226,17 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("d", ['key%s' % str(i) for i in range(10000)]), + hll_operations.hll_init("e", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("e", ['key%s' % str(i) for i in range(5000, 15000)]) + ] + client.operate(key, ops) + + _, _, bins = client.get(key) + values = [bins["e"]] + # Find the count of keys in the union of HLL bin "d" and all HLLs in values. (Should be around 15000) expr = exp.HLLGetUnionCount(values, exp.HLLBin("d")).compile() """ @@ -242,6 +264,17 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("d", ['key%s' % str(i) for i in range(10000)]), + hll_operations.hll_init("e", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("e", ['key%s' % str(i) for i in range(5000, 15000)]) + ] + client.operate(key, ops) + + _, _, bins = client.get(key) + values = [bins["e"]] + # Find the count of keys in the intersection of HLL bin "d" and all HLLs in values. (Should be around 5000) expr = exp.HLLGetIntersectCount(values, exp.HLLBin("d")).compile() """ @@ -269,6 +302,17 @@ def __init__(self, values: "TypeValue", bin: "TypeBinName"): # Let HLLBin "d" contain keys ['key%s' % str(i) for i in range(10000)]. # Let values be a list containing one HLL object with keys ['key%s' % str(i) for i in range(5000, 15000)]. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("d", ['key%s' % str(i) for i in range(10000)]), + hll_operations.hll_init("e", index_bit_count=8, mh_bit_count=8), + hll_operations.hll_add("e", ['key%s' % str(i) for i in range(5000, 15000)]) + ] + client.operate(key, ops) + + _, _, bins = client.get(key) + values = [bins["e"]] + # Find the similarity the HLL in values to HLL bin "d". (Should be around 0.33) # Note that similarity is defined as intersect(A, B, ...) / union(A, B, ...). expr = exp.HLLGetSimilarity(values, exp.HLLBin("d")).compile() From dcb6f458395336a7e7b6eb0456efcc2f951c1429 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 12:11:10 -0700 Subject: [PATCH 091/124] testsetup block has key defined. Make sure it runs --- aerospike_helpers/expressions/hll.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 7c18248ba9..c69a51b248 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -17,7 +17,6 @@ HyperLogLog expressions contain expressions for performing HLL operations. Most of these operations are equivalent to the :mod:`HyperLogLog API `. -# TODO: Run once for entire file .. testsetup:: from aerospike_helpers.operations import hll_operations From 9e348a14d149629acf6f80966f1c0dd30a281cb0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 12:27:41 -0700 Subject: [PATCH 092/124] Revert code changes used for debugging failing doctest errors. --- src/main/client/operate.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 8b70d23af0..8cf97362e1 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -1480,22 +1480,8 @@ static as_status get_operation(as_error *err, PyObject *op_dict, "Operation must contain an \"op\" entry"); } if (!PyLong_Check(py_operation)) { - PyTypeObject *py_op_type = Py_TYPE(py_operation); - if (!py_op_type) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Operation must be an integer, but got an " - "indeterminate type instead"); - } - const char *op_type_name = py_op_type->tp_name; - if (!op_type_name) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Operation must be an integer, but got an " - "indeterminate type instead"); - } return as_error_update(err, AEROSPIKE_ERR_PARAM, - "Operation must be an integer, but got a value " - "with type %s instead", - op_type_name); + "Operation must be an integer"); } *operation_ptr = PyLong_AsLong(py_operation); From 8c7b5500f7518483fd35ece86a6f01e2454d45da Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 12:38:38 -0700 Subject: [PATCH 093/124] Since testblock is hidden and there's no option to show it, have a duplicate code block that shows the testblock code. --- aerospike_helpers/expressions/hll.py | 15 +++++++++++++++ doc/aerospike_helpers.expressions.rst | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index c69a51b248..e3fb075d8d 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -19,11 +19,26 @@ .. testsetup:: + import aerospike + import aerospike_helpers.expressions as exp from aerospike_helpers.operations import hll_operations config = {"hosts": [("127.0.0.1", 3000)]} client = aerospike.client(config) key = ("test", "demo", 1) + +Assume all inline code examples run this beforehand: + +.. code-block:: Python + + import aerospike + import aerospike_helpers.expressions as exp + from aerospike_helpers.operations import hll_operations + + config = {"hosts": [("127.0.0.1", 3000)]} + client = aerospike.client(config) + key = ("test", "demo", 1) + """ # from __future__ import annotations diff --git a/doc/aerospike_helpers.expressions.rst b/doc/aerospike_helpers.expressions.rst index fcdc4f165f..48eb097167 100644 --- a/doc/aerospike_helpers.expressions.rst +++ b/doc/aerospike_helpers.expressions.rst @@ -166,6 +166,11 @@ Assume all in-line examples run this code beforehand: import aerospike import aerospike_helpers.expressions as exp +.. code-block:: Python + + import aerospike + import aerospike_helpers.expressions as exp + aerospike\_helpers\.expressions\.base module --------------------------------------------- From a758b7caf3e6bc40dfd8ff25fa4f0f0ae6f1dcd9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 15 May 2026 12:43:17 -0700 Subject: [PATCH 094/124] Rm dup import. --- aerospike_helpers/expressions/hll.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index e3fb075d8d..bb35ab9908 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -124,8 +124,6 @@ def __init__( .. testcode:: - from aerospike_helpers.operations import hll_operations - # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. ops = [ hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=8), From e9452d173e6f86f7e2fea0861aa8f56184780503 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:12:37 -0700 Subject: [PATCH 095/124] Use minimum supported python version for current sphinx version --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 71f40a36be..a5c2505c81 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -246,7 +246,7 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: ${{ env.LOWEST_SUPPORTED_PY_VERSION }} + python-version: 3.12 architecture: 'x64' - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 From 2688b0b7c710736255290cf7155c754b652ca7f3 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:15:54 -0700 Subject: [PATCH 096/124] Prevent either a failing stubtest or doctest run from affecting each other --- .github/workflows/smoke-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index a5c2505c81..752df3bf41 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -232,6 +232,7 @@ jobs: stubtest, doctest ] + fail-fast: false needs: build runs-on: ${{ needs.build.outputs.runner-os-used-for-build }} steps: From 2e9dcfd9543991ac12af81edaf607480c083a597 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:19:59 -0700 Subject: [PATCH 097/124] Use env var to determine python version to pull wheel and install python --- .github/workflows/smoke-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 752df3bf41..b0407ad462 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -234,6 +234,8 @@ jobs: ] fail-fast: false needs: build + env: + SPHINX_MINIMUM_SUPPORTED_PYTHON_VERSION: 3.12 runs-on: ${{ needs.build.outputs.runner-os-used-for-build }} steps: - name: Harden the runner (Audit all outbound calls) @@ -247,12 +249,12 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: 3.12 + python-version: ${{ env.SPHINX_MINIMUM_SUPPORTED_PYTHON_VERSION }} architecture: 'x64' - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: ${{ vars.GH_ARTIFACT_NAME_PREFIX_FOR_BUILDS }}-${{ env.LOWEST_SUPPORTED_PY_VERSION }}-${{ env.PLATFORM_TAG }} + pattern: ${{ vars.GH_ARTIFACT_NAME_PREFIX_FOR_BUILDS }}-${{ env.SPHINX_MINIMUM_SUPPORTED_PYTHON_VERSION }}-${{ env.PLATFORM_TAG }} - name: Install client run: pip install ./*.whl From 54de454c9a60ac1f1060f1dd8ad86412af91bbe9 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:07:28 -0700 Subject: [PATCH 098/124] Address regression where doctest doesn't have a server to run against --- .github/workflows/smoke-tests.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index b0407ad462..1d97f59667 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -233,7 +233,10 @@ jobs: doctest ] fail-fast: false - needs: build + needs: [ + get-env-vars, + build + ] env: SPHINX_MINIMUM_SUPPORTED_PYTHON_VERSION: 3.12 runs-on: ${{ needs.build.outputs.runner-os-used-for-build }} @@ -259,6 +262,15 @@ jobs: - name: Install client run: pip install ./*.whl + - if: ${{ matrix.test == 'doctest' }} + uses: aerospike/shared-workflows/.github/actions/setup-aerospike-server@CLIENT-4793-setup-aerospike-server-use-named-volumes-instead-of-bind-mounts-to-support-windows-and-macos-runners + with: + num-nodes: 1 + oidc-provider: ${{ vars.OIDC_PROVIDER_NAME }} + oidc-audience: ${{ vars.OIDC_AUDIENCE }} + server-tag: ${{ needs.get-env-vars.outputs.server-tag }} + server-container-repo: database-docker-virtual/aerospike-server + - name: Install test dependencies if: ${{ matrix.test == 'doctest' }} run: | From 37a28c6a5735d07c8f0a8d96d45328744cb161b0 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:16:52 -0700 Subject: [PATCH 099/124] Address doctest failures --- .github/workflows/smoke-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 1d97f59667..791bb706b5 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -270,6 +270,7 @@ jobs: oidc-audience: ${{ vars.OIDC_AUDIENCE }} server-tag: ${{ needs.get-env-vars.outputs.server-tag }} server-container-repo: database-docker-virtual/aerospike-server + config-file: ./.github/workflows/aerospike-ce.conf - name: Install test dependencies if: ${{ matrix.test == 'doctest' }} From bead2a725906a3b7e9c9acfe75563f345299a50e Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:37:20 -0700 Subject: [PATCH 100/124] Make HLLAdd code example more meaningful --- aerospike_helpers/expressions/hll.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index bb35ab9908..91d74cb6fb 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -136,16 +136,17 @@ def __init__( # Add ['key4', 'key5', 'key6'] so that the returned value is ['key1', 'key2', 'key3', 'key4', 'key5', # 'key6'] expr = exp.HLLAdd(None, ['key4', 'key5', 'key6'], 8, 8, exp.HLLBin("d")).compile() + expr_to_read = exp.HLLGetCount(exp.HLLBin("d")).compile() ops = [ expression_operations.expression_write("d", expr), - operations.read("d") + expression_operations.expression_read("d", expr_to_read) ] _, _, bins = client.operate(key, ops) print(bins["d"]) .. testoutput:: - HyperLogLog(...) + 6 """ self._children = ( list, From a468903cc892107feca34103c1136c31082c08e7 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:46:55 -0700 Subject: [PATCH 101/124] Provide more helpful output for HLLDescribe code example --- aerospike_helpers/expressions/hll.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 91d74cb6fb..3b125440d3 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -350,9 +350,24 @@ def __init__(self, bin: "TypeBinName"): Example: .. testcode:: + # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. + ops = [ + hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=16), + hll_operations.hll_add("d", ['key1', 'key2', 'key3']) + ] + client.operate(key, ops) # Get description of HLL bin "d". expr = exp.HLLDescribe(exp.HLLBin("d")).compile() + ops = [ + expression_operations.expression_read("d", expr), + ] + _, _, bins = client.operate(key, ops) + print(bins["d"]) + + .. testoutput:: + + [8, 16] """ self._children = (bin if isinstance(bin, _BaseExpr) else HLLBin(bin),) From 40d752f740120466b0492584f33a820cf4143b0c Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:20:40 -0700 Subject: [PATCH 102/124] Address doctest failures by fixing the testcode syntax. --- aerospike_helpers/expressions/hll.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 3b125440d3..5238cde885 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -350,6 +350,7 @@ def __init__(self, bin: "TypeBinName"): Example: .. testcode:: + # Let HLL bin "d" have the following elements, ['key1', 'key2', 'key3'], index_bits 8, mh_bits 8. ops = [ hll_operations.hll_init("d", index_bit_count=8, mh_bit_count=16), From 9703b25acaac95b56795b35b7f1221bf1e8bc301 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:38:39 -0700 Subject: [PATCH 103/124] Use f-strings and a separate python script to reduce code duplication --- aerospike_helpers/expressions/hll.py | 29 ++++++++++++++-------------- doc/client.rst | 28 ++++++--------------------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 5238cde885..97df48fe57 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -19,27 +19,28 @@ .. testsetup:: - import aerospike - import aerospike_helpers.expressions as exp - from aerospike_helpers.operations import hll_operations - - config = {"hosts": [("127.0.0.1", 3000)]} - client = aerospike.client(config) - key = ("test", "demo", 1) + {testsetup} Assume all inline code examples run this beforehand: .. code-block:: Python - import aerospike - import aerospike_helpers.expressions as exp - from aerospike_helpers.operations import hll_operations + {testsetup} +""" + +if __doc__: + __doc__ = __doc__.format( + """ + import aerospike + import aerospike_helpers.expressions as exp + from aerospike_helpers.operations import hll_operations - config = {"hosts": [("127.0.0.1", 3000)]} - client = aerospike.client(config) - key = ("test", "demo", 1) + config = {"hosts": [("127.0.0.1", 3000)]} + client = aerospike.client(config) + key = ("test", "demo", 1) + """ + ) -""" # from __future__ import annotations from typing import List, Union, Dict, Any diff --git a/doc/client.rst b/doc/client.rst index 8c2c4ab1d3..e46ec530d9 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -28,32 +28,16 @@ Boilerplate Code For Examples Assume every in-line example runs this code beforehand: -.. warning:: - Only run example code on a brand new Aerospike server. This code deletes all records in the ``demo`` set! - - .. testsetup:: - # Imports - import aerospike - from aerospike import exception as ex - import sys + import runpy + runpy.run_path("client-boilerplate.py") - # Configure the client - config = { - 'hosts': [ ('127.0.0.1', 3000)] - } - - # Create a client and connect it to the cluster - try: - client = aerospike.client(config) - client.truncate('test', "demo", 0) - except ex.ClientError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - sys.exit(1) +.. include:: client-boilerplate.py + :code: python - # Record key tuple: (namespace, set, key) - keyTuple = ('test', 'demo', 'key') +.. warning:: + Only run example code on a brand new Aerospike server. This code deletes all records in the ``demo`` set! Basic example: From 7fbc6827a1f0aa3fca90754e0a43a101981819e6 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:40:50 -0700 Subject: [PATCH 104/124] Fix python syntax --- aerospike_helpers/expressions/hll.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aerospike_helpers/expressions/hll.py b/aerospike_helpers/expressions/hll.py index 97df48fe57..c76d18c6cb 100644 --- a/aerospike_helpers/expressions/hll.py +++ b/aerospike_helpers/expressions/hll.py @@ -19,13 +19,13 @@ .. testsetup:: - {testsetup} + {0} Assume all inline code examples run this beforehand: .. code-block:: Python - {testsetup} + {0} """ if __doc__: From 44ab35640f88e0b5111318ae5e8cfc70f6070d69 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:43:33 -0700 Subject: [PATCH 105/124] Add missing boilerplate script --- doc/client-boilerplate.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 doc/client-boilerplate.py diff --git a/doc/client-boilerplate.py b/doc/client-boilerplate.py new file mode 100644 index 0000000000..74a7adff80 --- /dev/null +++ b/doc/client-boilerplate.py @@ -0,0 +1,20 @@ +# Imports +import aerospike +from aerospike import exception as ex +import sys + +# Configure the client +config = { + 'hosts': [ ('127.0.0.1', 3000)] +} + +# Create a client and connect it to the cluster +try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) +except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + +# Record key tuple: (namespace, set, key) +keyTuple = ('test', 'demo', 'key') From 3eae3641ad8423e9f2583d3bc466af939292b2e2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:53:59 -0700 Subject: [PATCH 106/124] Rerun doctest if the boilerplate testsetup code changes --- .github/workflows/smoke-tests.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 791bb706b5..3f710e1df8 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -12,16 +12,18 @@ on: # Using push instead of pull_request so that commits with no applicable code changes will not trigger a rebuild # This saves compute cost push: - paths-ignore: - # Only doc related changes - - doc/**/* - - .readthedocs.yml + paths: + # We run doctest in this workflow + - '!doc/**/*' + - 'doc/client-boilerplate.py' + # Only for building docs + - '!.readthedocs.yml' # Only for ignoring git commits when using 'git blame' - - .git-blame-ignore-revs + - '!.git-blame-ignore-revs' # Only used by QE for building their own wheels - - .build.yml + - '!.build.yml' # Other misc files not related to source code changes - - benchmarks/**/* + - '!benchmarks/**/*' tags-ignore: - '**' # Have to add this or tags-ignore will cause workflow to never run From e46eae2256b33111defca5c716d0a926a75ee712 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:56:30 -0700 Subject: [PATCH 107/124] Fix smoke tests not triggering. --- .github/workflows/smoke-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 3f710e1df8..287c31423d 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -12,7 +12,10 @@ on: # Using push instead of pull_request so that commits with no applicable code changes will not trigger a rebuild # This saves compute cost push: + # We have to use paths key to allow changes in the boilerplate code in doc/ to trigger doctest + # paths-ignore does not support ! operator paths: + - '**/*' # We run doctest in this workflow - '!doc/**/*' - 'doc/client-boilerplate.py' From 7104128513788d8665a6c6caac9d13398ecfb230 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:22:01 -0700 Subject: [PATCH 108/124] Revert "Add missing boilerplate script" This reverts commit 44ab35640f88e0b5111318ae5e8cfc70f6070d69. --- doc/client-boilerplate.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 doc/client-boilerplate.py diff --git a/doc/client-boilerplate.py b/doc/client-boilerplate.py deleted file mode 100644 index 74a7adff80..0000000000 --- a/doc/client-boilerplate.py +++ /dev/null @@ -1,20 +0,0 @@ -# Imports -import aerospike -from aerospike import exception as ex -import sys - -# Configure the client -config = { - 'hosts': [ ('127.0.0.1', 3000)] -} - -# Create a client and connect it to the cluster -try: - client = aerospike.client(config) - client.truncate('test', "demo", 0) -except ex.ClientError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - sys.exit(1) - -# Record key tuple: (namespace, set, key) -keyTuple = ('test', 'demo', 'key') From 7f0c5a89f4169782de1fdebf9426c479c77c56a4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:23:27 -0700 Subject: [PATCH 109/124] Revert "Fix smoke tests not triggering." This reverts commit e46eae2256b33111defca5c716d0a926a75ee712. --- .github/workflows/smoke-tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 287c31423d..3f710e1df8 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -12,10 +12,7 @@ on: # Using push instead of pull_request so that commits with no applicable code changes will not trigger a rebuild # This saves compute cost push: - # We have to use paths key to allow changes in the boilerplate code in doc/ to trigger doctest - # paths-ignore does not support ! operator paths: - - '**/*' # We run doctest in this workflow - '!doc/**/*' - 'doc/client-boilerplate.py' From 7b153cb322b057360a6a74fb0892a15bf7ab7537 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:23:34 -0700 Subject: [PATCH 110/124] Revert "Rerun doctest if the boilerplate testsetup code changes" This reverts commit 3eae3641ad8423e9f2583d3bc466af939292b2e2. --- .github/workflows/smoke-tests.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 3f710e1df8..791bb706b5 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -12,18 +12,16 @@ on: # Using push instead of pull_request so that commits with no applicable code changes will not trigger a rebuild # This saves compute cost push: - paths: - # We run doctest in this workflow - - '!doc/**/*' - - 'doc/client-boilerplate.py' - # Only for building docs - - '!.readthedocs.yml' + paths-ignore: + # Only doc related changes + - doc/**/* + - .readthedocs.yml # Only for ignoring git commits when using 'git blame' - - '!.git-blame-ignore-revs' + - .git-blame-ignore-revs # Only used by QE for building their own wheels - - '!.build.yml' + - .build.yml # Other misc files not related to source code changes - - '!benchmarks/**/*' + - benchmarks/**/* tags-ignore: - '**' # Have to add this or tags-ignore will cause workflow to never run From 82eba7dfb046ba06fd1428c3ca6df3a947237a0d Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:24:52 -0700 Subject: [PATCH 111/124] Always run doctest when docs/ has changes. --- .github/workflows/smoke-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 791bb706b5..58dbed3090 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -13,8 +13,8 @@ on: # This saves compute cost push: paths-ignore: - # Only doc related changes - - doc/**/* + # Changes to code examples in API docs may affect doctest results + # - doc/**/* - .readthedocs.yml # Only for ignoring git commits when using 'git blame' - .git-blame-ignore-revs From c3bd949ff7947a6e58d6aa172cdbd17b473787a1 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:27:34 -0700 Subject: [PATCH 112/124] Add back duplicated python setup code as a workaround for testsetup not being able to take in a script file --- doc/client.rst | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index e46ec530d9..5c450f9ba7 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -28,13 +28,54 @@ Boilerplate Code For Examples Assume every in-line example runs this code beforehand: +.. Would prefer to have an external Python script to reduce code duplication here. +.. But there's no way to call a Python script for testsetup + .. testsetup:: - import runpy - runpy.run_path("client-boilerplate.py") + # Imports + import aerospike + from aerospike import exception as ex + import sys + + # Configure the client + config = { + 'hosts': [ ('127.0.0.1', 3000)] + } + + # Create a client and connect it to the cluster + try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) + except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + + # Record key tuple: (namespace, set, key) + keyTuple = ('test', 'demo', 'key') + +.. code-block:: python + + # Imports + import aerospike + from aerospike import exception as ex + import sys + + # Configure the client + config = { + 'hosts': [ ('127.0.0.1', 3000)] + } + + # Create a client and connect it to the cluster + try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) + except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) -.. include:: client-boilerplate.py - :code: python + # Record key tuple: (namespace, set, key) + keyTuple = ('test', 'demo', 'key') .. warning:: Only run example code on a brand new Aerospike server. This code deletes all records in the ``demo`` set! From 621ecaf2e2b38d00bc03963dd463ddf819913873 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:34:17 -0700 Subject: [PATCH 113/124] Lock setup-aerospike-server for doctest. --- .github/workflows/smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 7f0a4c9ba5..5dc5da4935 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -263,7 +263,7 @@ jobs: run: pip install ./*.whl - if: ${{ matrix.test == 'doctest' }} - uses: aerospike/shared-workflows/.github/actions/setup-aerospike-server@CLIENT-4793-setup-aerospike-server-use-named-volumes-instead-of-bind-mounts-to-support-windows-and-macos-runners + uses: aerospike/shared-workflows/.github/actions/setup-aerospike-server@bd168dedaa4fc0b17a560541779d815540eded2d # v3.7.0 with: num-nodes: 1 oidc-provider: ${{ vars.OIDC_PROVIDER_NAME }} From 431ca2d55c8603b27dc1b214da29c2511f04f3a4 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:41:57 -0700 Subject: [PATCH 114/124] Dont think these comments matter anymore if the code example for client.udf_put() was removed --- doc/client.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 5c450f9ba7..3a4806a910 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -657,10 +657,6 @@ User Defined Functions .. note:: To run this example, do not run the boilerplate code. - .. TODO - probably there is better syntax than using emphasize-lines with hardcoded numbers - - .. :emphasize-lines: 5,9 - .. method:: udf_remove(module[, policy: dict]) Remove a previously registered UDF module from the cluster. From e3cd0faee181b2e6bece08e4a4a685ff22aa3005 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:43:42 -0700 Subject: [PATCH 115/124] Remove old mock logic now that autodoc_mock_imports is used. --- doc/conf.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 94c23ce51d..eaacfc55cf 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -2,30 +2,14 @@ import sys, os -# try: -# from unittest.mock import MagicMock -# except ImportError: -# try: -# from mock import Mock as MagicMock -# except ImportError as e: -# print("mock is missing: pip install mock") -# raise e - # 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.append(os.path.abspath(os.path.join(os.path.dirname(__name__), ".."))) -# Mock out aerospike, +# Mock out aerospike if it's not installed # see https://docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules - -# class Mock(MagicMock): -# @classmethod -# def __getattr__(cls, name): -# return MagicMock() - - try: import aerospike except ImportError: From fb0850f53ea26f9d0f54c07f76885aa430f1012b Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:57:43 -0700 Subject: [PATCH 116/124] Elaborate the format of client.info_all()'s output since the tested code example doesn't show the MAC address anymore. --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index 3a4806a910..74d33054ed 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -822,7 +822,7 @@ Info Operations :param str command: see `Info Command Reference `_. :param dict policy: optional :ref:`aerospike_info_policies`. - :rtype: :class:`dict` + :rtype: :class:`dict` mapping the node name to a tuple with the second element being the info response string or :py:obj:`None`. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. .. testcode:: From d4999e64d453d7bfe8bb2ae0b0d120097c8595be Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:32:01 -0700 Subject: [PATCH 117/124] Use testcode block instead of doctest. Doesn't make sense to print out the zero result codes from put and append. --- doc/client.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 74d33054ed..2e18adf07f 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -481,14 +481,15 @@ String Operations :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. doctest:: - - >>> client.put(keyTuple, {'bin1': 'Martin Luther King'}) - 0 - >>> client.append(keyTuple, 'bin1', ' jr.') - 0 - >>> (_, _, bins) = client.get(keyTuple) - >>> print(bins) + .. testcode:: + + client.put(keyTuple, {'bin1': 'Martin Luther King'}) + client.append(keyTuple, 'bin1', ' jr.') + (_, _, bins) = client.get(keyTuple) + print(bins) + + .. testoutput:: + {'bin1': 'Martin Luther King jr.'} .. method:: prepend(key, bin, val[, meta: dict[, policy: dict]]) From fab78ed4156c04694328dd26a9c48d31dd8f3e14 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:01:00 -0700 Subject: [PATCH 118/124] Address remaining codeblocks that weren't tested. Not testing scan.rst since it's deprecated --- doc/exception.rst | 13 ++++++---- doc/geojson.rst | 13 +++++----- doc/predicates.rst | 16 ++++++------- doc/query.rst | 59 ++++++++++++++++++++++++++-------------------- 4 files changed, 57 insertions(+), 44 deletions(-) diff --git a/doc/exception.rst b/doc/exception.rst index 3ad3f3b2f1..b48bf5c24d 100644 --- a/doc/exception.rst +++ b/doc/exception.rst @@ -13,18 +13,23 @@ Example This is a simple example on how to catch an exception thrown by the Aerospike client: -.. code-block:: python +.. testcode:: import aerospike from aerospike import exception as ex try: - config = { 'hosts': [ ('127.0.0.1', 3000)], 'policies': { 'total_timeout': 1200}} + # Invalid port number + config = { 'hosts': [ ('127.0.0.1', 5000)], 'policies': { 'total_timeout': 1200}} client = aerospike.client(config) client.close() except ex.AerospikeError as e: print("Error: {0} [{1}]".format(e.msg, e.code)) +.. testoutput:: + + TODO - need to set testoutput + .. versionadded:: 1.0.44 Base Class @@ -749,13 +754,13 @@ In Doubt Status --------------- The ``in-doubt`` status of a caught exception can be checked by looking at the 5th element of its `args` tuple: - .. code-block:: python + .. testcode:: key = 'test', 'demo', 1 record = {'some': 'thing'} try: client.put(key, record) except AerospikeError as exc: - print("The in doubt nature of the operation is: {}".format(exc.args[4]) + print("The in doubt nature of the operation is: {}".format(exc.args[4])) .. versionadded:: 3.0.1 diff --git a/doc/geojson.rst b/doc/geojson.rst index d8e616486b..385f793873 100755 --- a/doc/geojson.rst +++ b/doc/geojson.rst @@ -37,7 +37,7 @@ deserialized into a :class:`~aerospike.GeoJSON` instance. Example ------- -.. code-block:: python +.. testcode:: import aerospike from aerospike import GeoJSON @@ -54,9 +54,6 @@ Example 'coordinates': [longitude, latitude]}) print(loc) - # Expected output: - # {"type": "Point", "coordinates": [-80.604333, 28.608389]} - # Alternatively, create the GeoJSON point from a string loc = aerospike.geojson('{"type": "Point", "coordinates": [-80.604333, 28.608389]}') @@ -69,13 +66,15 @@ Example (k, m, b) = client.get(('test', 'pads', 'launchpad1')) print(b) - # Expected output: - # {'pad_id': 1, 'loc': '{"type": "Point", "coordinates": [-80.604333, 28.608389]}'} - # Cleanup client.remove(('test', 'pads', 'launchpad1')) client.close() +.. testoutput:: + + {"type": "Point", "coordinates": [-80.604333, 28.608389]} + {'pad_id': 1, 'loc': '{"type": "Point", "coordinates": [-80.604333, 28.608389]}'} + Methods ======= diff --git a/doc/predicates.rst b/doc/predicates.rst index 00e6979e36..0f9720aec4 100644 --- a/doc/predicates.rst +++ b/doc/predicates.rst @@ -22,7 +22,7 @@ Bin Predicates :param int max: the maximum value to be matched with the between operator. :return: `tuple` to be used in :meth:`aerospike.Query.where`. - .. code-block:: python + .. testcode:: import aerospike from aerospike import predicates as p @@ -45,7 +45,7 @@ Bin Predicates :type val: :py:class:`str` or :py:class:`int` :return: `tuple` to be used in :meth:`aerospike.Query.where`. - .. code-block:: python + .. testcode:: import aerospike from aerospike import predicates as p @@ -75,7 +75,7 @@ GeoJSON Predicates .. note:: Requires server version >= 3.7.0 - .. code-block:: python + .. testcode:: import aerospike from aerospike import GeoJSON @@ -126,7 +126,7 @@ GeoJSON Predicates .. note:: Requires server version >= 3.8.1 - .. code-block:: python + .. testcode:: import aerospike from aerospike import GeoJSON @@ -163,7 +163,7 @@ GeoJSON Predicates .. note:: Requires server version >= 3.7.0 - .. code-block:: python + .. testcode:: import aerospike from aerospike import GeoJSON @@ -210,7 +210,7 @@ GeoJSON Predicates .. note:: Requires server version >= 3.7.0 - .. code-block:: python + .. testcode:: import aerospike from aerospike import GeoJSON @@ -255,7 +255,7 @@ Map and List Predicates .. note:: Requires server version >= 3.8.1 - .. code-block:: python + .. testcode:: import aerospike from aerospike import predicates as p @@ -292,7 +292,7 @@ Map and List Predicates .. note:: Requires server version >= 3.8.1 - .. code-block:: python + .. testcode:: import aerospike from aerospike import predicates as p diff --git a/doc/query.rst b/doc/query.rst index 6070bd600e..31d0272246 100755 --- a/doc/query.rst +++ b/doc/query.rst @@ -184,7 +184,7 @@ Assume this boilerplate code is run before all examples below: "partition_filter" see :ref:`aerospike_partition_objects` can be used to specify which partitions/records results will query. See the example below. - .. code-block:: python + .. testcode:: # This is an example of querying partitions 1000 - 1003. import aerospike @@ -229,7 +229,7 @@ Assume this boilerplate code is run before all examples below: "partition_filter" see :ref:`aerospike_partition_objects` can be used to specify which partitions/records foreach will query. See the example below. - .. code-block:: python + .. testcode:: # This is an example of querying partitions 1000 - 1003. import aerospike @@ -328,7 +328,7 @@ Assume this boilerplate code is run before all examples below: :return: a job ID that can be used with :meth:`~aerospike.Client.job_info` to track the status of the ``aerospike.JOB_QUERY`` , as it runs in the background. - .. code-block:: python + .. testcode:: # EXAMPLE 1: Increase everyone's score by 100 @@ -346,10 +346,6 @@ Assume this boilerplate code is run before all examples below: for key in keyTuples: _, _, bins = client.get(key) print(bins) - # {"score": 200, "elo": 1400} - # {"score": 120, "elo": 1500} - # {"score": 110, "elo": 1100} - # {"score": 300, "elo": 900} # EXAMPLE 2: Increase score by 100 again for those with elos > 1000 # Use write policy to select players by elo @@ -365,16 +361,23 @@ Assume this boilerplate code is run before all examples below: for i, key in enumerate(keyTuples): _, _, bins = client.get(key) print(bins) - # {"score": 300, "elo": 1400} <-- - # {"score": 220, "elo": 1500} <-- - # {"score": 210, "elo": 1100} <-- - # {"score": 300, "elo": 900} # Cleanup and close the connection to the Aerospike cluster. for key in keyTuples: client.remove(key) client.close() + .. testoutput:: + + {"score": 200, "elo": 1400} + {"score": 120, "elo": 1500} + {"score": 110, "elo": 1100} + {"score": 300, "elo": 900} + {"score": 300, "elo": 1400} + {"score": 220, "elo": 1500} + {"score": 210, "elo": 1100} + {"score": 300, "elo": 900} + .. method:: paginate() Makes a query instance a paginated query. @@ -385,7 +388,7 @@ Assume this boilerplate code is run before all examples below: This can be retrieved later using .get_partitions_status(). This can also been done by using the partition_filter policy. - .. code-block:: python + .. testcode:: # After inserting 4 records... # Query 3 pages of 2 records each. @@ -409,14 +412,17 @@ Assume this boilerplate code is run before all examples below: if query.is_done(): print("all done") break - # got page: 0 - # (('test', 'demo', None, bytearray(b'HD\xd1\xfa$L\xa0\xf5\xa2~\xd6\x1dv\x91\x9f\xd6\xfa\xad\x18\x00')), {'ttl': 2591996, 'gen': 1}, {'score': 20, 'elo': 1500}) - # (('test', 'demo', None, bytearray(b'f\xa4\t"\xa9uc\xf5\xce\x97\xf0\x16\x9eI\xab\x89Q\xb8\xef\x0b')), {'ttl': 2591996, 'gen': 1}, {'score': 10, 'elo': 1100}) - # got page: 1 - # (('test', 'demo', None, bytearray(b'\xb6\x9f\xf5\x7f\xfarb.IeaVc\x17n\xf4\x9b\xad\xa7T')), {'ttl': 2591996, 'gen': 1}, {'score': 200, 'elo': 900}) - # (('test', 'demo', None, bytearray(b'j>@\xfe\xe0\x94\xd5?\n\xd7\xc3\xf2\xd7\x045\xbc*\x07 \x1a')), {'ttl': 2591996, 'gen': 1}, {'score': 100, 'elo': 1400}) - # got page: 2 - # all done + + .. testoutput:: + + got page: 0 + (('test', 'demo', None, bytearray(b'HD\xd1\xfa$L\xa0\xf5\xa2~\xd6\x1dv\x91\x9f\xd6\xfa\xad\x18\x00')), {'ttl': 2591996, 'gen': 1}, {'score': 20, 'elo': 1500}) + (('test', 'demo', None, bytearray(b'f\xa4\t"\xa9uc\xf5\xce\x97\xf0\x16\x9eI\xab\x89Q\xb8\xef\x0b')), {'ttl': 2591996, 'gen': 1}, {'score': 10, 'elo': 1100}) + got page: 1 + (('test', 'demo', None, bytearray(b'\xb6\x9f\xf5\x7f\xfarb.IeaVc\x17n\xf4\x9b\xad\xa7T')), {'ttl': 2591996, 'gen': 1}, {'score': 200, 'elo': 900}) + (('test', 'demo', None, bytearray(b'j>@\xfe\xe0\x94\xd5?\n\xd7\xc3\xf2\xd7\x045\xbc*\x07 \x1a')), {'ttl': 2591996, 'gen': 1}, {'score': 100, 'elo': 1400}) + got page: 2 + all done .. method:: is_done() @@ -435,7 +441,7 @@ Assume this boilerplate code is run before all examples below: :return: See :ref:`aerospike_partition_objects` for a description of the partition status return value. - .. code-block:: python + .. testcode:: # Only read 2 records @@ -452,8 +458,6 @@ Assume this boilerplate code is run before all examples below: query = client.query("test", "demo") query.paginate() query.foreach(callback) - # (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 10, 'elo': 1100}) - # (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 20, 'elo': 1500}) # Use this to resume query where we left off @@ -471,8 +475,13 @@ Assume this boilerplate code is run before all examples below: } query.foreach(resume_callback, policy) - # 1096 -> (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 100, 'elo': 1400}) - # 3690 -> (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 200, 'elo': 900}) + + .. testoutput:: + + (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 10, 'elo': 1100}) + (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 20, 'elo': 1500}) + 1096 -> (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 100, 'elo': 1400}) + 3690 -> (('test', 'demo', None, bytearray(b'...')), {'ttl': 2591996, 'gen': 1}, {'score': 200, 'elo': 900}) .. _aerospike_query_policies: From 2c4b585caf753e991b3d9f8dffa0bca49dba5c65 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:08:13 -0700 Subject: [PATCH 119/124] Fix code example. Address inconsistent indenting --- doc/exception.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/exception.rst b/doc/exception.rst index b48bf5c24d..20379255c2 100644 --- a/doc/exception.rst +++ b/doc/exception.rst @@ -28,7 +28,7 @@ This is a simple example on how to catch an exception thrown by the Aerospike cl .. testoutput:: - TODO - need to set testoutput + Error: Failed to connect [-10] .. versionadded:: 1.0.44 @@ -756,11 +756,20 @@ In Doubt Status .. testcode:: - key = 'test', 'demo', 1 - record = {'some': 'thing'} - try: + import aerospike + from aerospike import exception as ex + + # Configure the client + config = { + 'hosts': [ ('127.0.0.1', 3000)] + } + client = aerospike.client(config) + + key = 'test', 'demo', 1 + record = {'some': 'thing'} + try: client.put(key, record) - except AerospikeError as exc: + except ex.AerospikeError as exc: print("The in doubt nature of the operation is: {}".format(exc.args[4])) .. versionadded:: 3.0.1 From 5e810277771937e187c76a419532b7eccfd28561 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:28:19 -0700 Subject: [PATCH 120/124] Ignore these code examples since they don't work independently --- doc/predicates.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/predicates.rst b/doc/predicates.rst index 0f9720aec4..5815420b93 100644 --- a/doc/predicates.rst +++ b/doc/predicates.rst @@ -22,7 +22,7 @@ Bin Predicates :param int max: the maximum value to be matched with the between operator. :return: `tuple` to be used in :meth:`aerospike.Query.where`. - .. testcode:: + .. code-block:: Python import aerospike from aerospike import predicates as p @@ -45,7 +45,7 @@ Bin Predicates :type val: :py:class:`str` or :py:class:`int` :return: `tuple` to be used in :meth:`aerospike.Query.where`. - .. testcode:: + .. code-block:: Python import aerospike from aerospike import predicates as p From e051e8e221eff09772caddc270bb95468ecac3b2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:30:41 -0700 Subject: [PATCH 121/124] Add missing boilerplate. I think this code example is worth keeping since it's useful --- doc/query.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/query.rst b/doc/query.rst index 31d0272246..b03998f96d 100755 --- a/doc/query.rst +++ b/doc/query.rst @@ -443,6 +443,15 @@ Assume this boilerplate code is run before all examples below: .. testcode:: + import aerospike + # Configure the client + config = { + 'hosts': [('127.0.0.1', 3000)] + } + + # Create a client and connect it to the cluster + client = aerospike.client(config) + # Only read 2 records recordCount = 0 From bf0cb4c2c2f87509994de3ba25bf96a0601b45c2 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:20:02 -0700 Subject: [PATCH 122/124] Manually move some of the code examples in client.rst to testcode directives. TODO see if there's a way to dynamically add these testcode blocks using the code examples in doc/examples. --- doc/client.rst | 158 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 142 insertions(+), 16 deletions(-) diff --git a/doc/client.rst b/doc/client.rst index 2e18adf07f..491c7993e0 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -162,8 +162,19 @@ Record Commands Example: - .. include:: examples/put.py - :code: python + .. testcode:: + + # Insert a record with bin1 + client.put(keyTuple, {'bin1': 4}) + + # Insert another bin named bin2 + client.put(keyTuple, {'bin2': "value"}) + + # Remove bin1 from this record + client.put(keyTuple, {'bin2': aerospike.null()}) + + # Removing the last bin should delete this record + client.put(keyTuple, {'bin1': aerospike.null()}) .. method:: exists(key[, policy: dict]) -> (key, meta) @@ -180,8 +191,27 @@ Record Commands :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/exists.py - :code: python + .. testcode:: + + # Check non-existent record + (key, meta) = client.exists(keyTuple) + + print(key) + print(meta) + + # Check existing record + client.put(keyTuple, {'bin1': 4}) + (key, meta) = client.exists(keyTuple) + + print(key) + print(meta) + + .. testblock:: + + ('test', 'demo', 'key', bytearray(b'...')) + None + ('test', 'demo', 'key', bytearray(b'...')) + {'ttl': 2592000, 'gen': 1} .. versionchanged:: 2.0.3 @@ -196,8 +226,22 @@ Record Commands :raises: :exc:`~aerospike.exception.RecordNotFound`. - .. include:: examples/get.py - :code: python + .. testcode:: + + # Get nonexistent record + try: + client.get(keyTuple) + except ex.RecordNotFound as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + # Error: 127.0.0.1:3000 AEROSPIKE_ERR_RECORD_NOT_FOUND [2] + + # Get existing record + client.put(keyTuple, {'bin1': 4}) + (key, meta, bins) = client.get(keyTuple) + + print(key) # ('test', 'demo', None, bytearray(b'...')) + print(meta) # {'ttl': 2592000, 'gen': 1} + print(bins) # {'bin1': 4} .. versionchanged:: 2.0.0 @@ -215,8 +259,26 @@ Record Commands :raises: :exc:`~aerospike.exception.RecordNotFound`. - .. include:: examples/select.py - :code: python + .. testcode:: + + # Record to select from + client.put(keyTuple, {'bin1': 4, 'bin2': 3}) + + # Only get bin1 + (key, meta, bins) = client.select(keyTuple, ['bin1']) + + # Similar output to get() + print(key) # ('test', 'demo', 'key', bytearray(b'...')) + print(meta) # {'ttl': 2592000, 'gen': 1} + print(bins) # {'bin1': 4} + + # Get all bins + (key, meta, bins) = client.select(keyTuple, ['bin1', 'bin2']) + print(bins) # {'bin1': 4, 'bin2': 3} + + # Get nonexistent bin + (key, meta, bins) = client.select(keyTuple, ['bin3']) + print(bins) # {} .. versionchanged:: 2.0.0 @@ -238,8 +300,29 @@ Record Commands :return: a :ref:`aerospike_record_tuple`. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/operate.py - :code: python + .. testcode:: + + from aerospike_helpers.operations import operations + + # Add name, update age, and return attributes + client.put(keyTuple, {'age': 25, 'career': 'delivery boy'}) + ops = [ + operations.increment("age", 1000), + operations.write("name", "J."), + operations.prepend("name", "Phillip "), + operations.append("name", " Fry"), + operations.read("name"), + operations.read("career"), + operations.read("age") + ] + (key, meta, bins) = client.operate(key, ops) + + print(key) # ('test', 'demo', None, bytearray(b'...')) + # The generation should only increment once + # A transaction is *atomic* + print(meta) # {'ttl': 2592000, 'gen': 2} + print(bins) # Will display all bins selected by read operations + # {'name': 'Phillip J. Fry', 'career': 'delivery boy', 'age': 1025} .. note:: @@ -267,8 +350,27 @@ Record Commands :return: a :ref:`aerospike_record_tuple`. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/operate_ordered.py - :code: python + .. testcode:: + + from aerospike_helpers.operations import operations + + # Add name, update age, and return attributes + client.put(keyTuple, {'age': 25, 'career': 'delivery boy'}) + ops = [ + operations.increment("age", 1000), + operations.write("name", "J."), + operations.prepend("name", "Phillip "), + operations.append("name", " Fry"), + operations.read("name"), + operations.read("career"), + operations.read("age") + ] + (key, meta, bins) = client.operate_ordered(keyTuple, ops) + + # Same output for key and meta as operate() + # But read operations are outputted as bin-value pairs + print(bins) + # [('name': 'Phillip J. Fry'), ('career': 'delivery boy'), ('age': 1025)] .. versionchanged:: 2.1.3 @@ -287,8 +389,20 @@ Record Commands :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/touch.py - :code: python + .. testcode:: + + # Insert record and get its metadata + client.put(keyTuple, bins = {"bin1": 4}) + (key, meta) = client.exists(keyTuple) + print(meta) # {'ttl': 2592000, 'gen': 1} + + # Explicitly set TTL to 120 + # and increment generation + client.touch(keyTuple, 120) + + # Record metadata should be updated + (key, meta) = client.exists(keyTuple) + print(meta) # {'ttl': 120, 'gen': 2} .. method:: remove(key[meta: dict[, policy: dict]]) @@ -304,8 +418,20 @@ Record Commands :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/remove.py - :code: python + .. testcode:: + + # Insert a record + client.put(keyTuple, {"bin1": 4}) + + # Try to remove it with the wrong generation + try: + client.remove(keyTuple, meta={'gen': 5}, policy={'gen': aerospike.POLICY_GEN_EQ}) + except ex.AerospikeError as e: + # Error: AEROSPIKE_ERR_RECORD_GENERATION [3] + print("Error: {0} [{1}]".format(e.msg, e.code)) + + # Remove it ignoring generation + client.remove(keyTuple) .. method:: remove_bin(key, list[, meta: dict[, policy: dict]]) From e6564b9c7e169c4a3799849bf1e2d4914c885815 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:04:49 -0700 Subject: [PATCH 123/124] Move all code examples from doc/examples to testcode blocks for sphinx doctest plugin to pick up. Delete the example python scripts now that they are inlined in the rst files --- doc/aerospike.rst | 118 +++++++++++++- doc/aerospike_helpers.expressions.rst | 60 +++++++- doc/client.rst | 212 ++++++++++++++++++++++++-- doc/examples/batch_apply.py | 25 --- doc/examples/batch_operate.py | 30 ---- doc/examples/batch_remove.py | 21 --- doc/examples/batch_write.py | 56 ------- doc/examples/exists.py | 12 -- doc/examples/expressions/top.py | 56 ------- doc/examples/get.py | 14 -- doc/examples/get_cdtctx_base64.py | 11 -- doc/examples/get_expression_base64.py | 8 - doc/examples/keyordereddict.py | 46 ------ doc/examples/log.py | 34 ----- doc/examples/lua/lua.py | 41 ----- doc/examples/operate.py | 21 --- doc/examples/operate_ordered.py | 19 --- doc/examples/put.py | 11 -- doc/examples/query/boilerplate.py | 38 ----- doc/examples/query/foreach.py | 19 --- doc/examples/query/foreachfalse.py | 20 --- doc/examples/query/results.py | 9 -- doc/examples/remove.py | 12 -- doc/examples/remove_bin.py | 11 -- doc/examples/scan/top.py | 46 ------ doc/examples/select.py | 18 --- doc/examples/serializer.py | 76 --------- doc/examples/touch.py | 12 -- doc/examples/truncate.py | 18 --- doc/key_ordered_dict.rst | 50 +++++- doc/query.rst | 188 +++++++++++++++++++++-- doc/scan.rst | 50 +++++- 32 files changed, 642 insertions(+), 720 deletions(-) delete mode 100644 doc/examples/batch_apply.py delete mode 100644 doc/examples/batch_operate.py delete mode 100644 doc/examples/batch_remove.py delete mode 100644 doc/examples/batch_write.py delete mode 100644 doc/examples/exists.py delete mode 100644 doc/examples/expressions/top.py delete mode 100644 doc/examples/get.py delete mode 100644 doc/examples/get_cdtctx_base64.py delete mode 100644 doc/examples/get_expression_base64.py delete mode 100644 doc/examples/keyordereddict.py delete mode 100644 doc/examples/log.py delete mode 100644 doc/examples/lua/lua.py delete mode 100644 doc/examples/operate.py delete mode 100644 doc/examples/operate_ordered.py delete mode 100644 doc/examples/put.py delete mode 100644 doc/examples/query/boilerplate.py delete mode 100644 doc/examples/query/foreach.py delete mode 100644 doc/examples/query/foreachfalse.py delete mode 100644 doc/examples/query/results.py delete mode 100644 doc/examples/remove.py delete mode 100644 doc/examples/remove_bin.py delete mode 100644 doc/examples/scan/top.py delete mode 100644 doc/examples/select.py delete mode 100644 doc/examples/serializer.py delete mode 100644 doc/examples/touch.py delete mode 100644 doc/examples/truncate.py diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 0d71659b0b..6b91a4930a 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -255,8 +255,84 @@ The following example shows the three modes of serialization: 2. Class-level user functions 3. Instance-level user functions -.. include:: examples/serializer.py - :code: python +.. testcode:: + + import aerospike + import marshal + import json + + # Serializers and deserializers + # Both local and global serializers use json library + # Functions print which one is being used + + def classSerializer(obj): + print("Using class serializer") + return json.dumps(obj) + + def classDeserializer(bytes): + print("Using class deserializer") + return json.loads(bytes) + + def localSerializer(obj): + print("Using local serializer") + return json.dumps(obj) + + def localDeserializer(bytes): + print("Using local deserializer") + return json.loads(bytes) + + # First client has class-level serializer set in aerospike module + aerospike.set_serializer(classSerializer) + aerospike.set_deserializer(classDeserializer) + config = { + 'hosts': [('127.0.0.1', 3000)] + } + client = aerospike.client(config) + + # Second client has instance-level serializer set in client config + config['serialization'] = (localSerializer, localDeserializer) + client2 = aerospike.client(config) + + # Keys: foo1, foo2, foo3 + keys = [('test', 'demo', f'foo{i}') for i in range(1, 4)] + # Tuple is an unsupported type + tupleBin = {'bin': (1, 2, 3)} + + # Use the default, built-in serialization (cPickle) + client.put(keys[0], tupleBin) + (_, _, bins) = client.get(keys[0]) + print(bins) + + # Expected output: + # {'bin': (1, 2, 3)} + + # First client uses class-level, user-defined serialization + # No instance-level serializer was declared + client.put(keys[1], tupleBin, serializer=aerospike.SERIALIZER_USER) + (_, _, bins) = client.get(keys[1]) + # Notice that json-encoding a tuple produces a list + print(bins) + + # Expected output: + # Using class serializer + # Using class deserializer + # {'bin': [1, 2, 3]} + + # Second client uses instance-level, user-defined serialization + # Instance-level serializer overrides class-level serializer + client2.put(keys[2], tupleBin, serializer=aerospike.SERIALIZER_USER) + (_, _, bins) = client2.get(keys[2]) + print(bins) + + # Expected output: + # Using local serializer + # Using local deserializer + # {'bin': [1, 2, 3]} + + # Cleanup + client.batch_remove(keys) + client.close() + client2.close() Records ``foo1`` and ``foo2`` should have different encodings from each other since they use different serializers. (record ``foo3`` uses the same encoding as ``foo2``) @@ -301,8 +377,42 @@ By default: The following example shows several different methods to configuring logging for the Aerospike Python Client: -.. include:: examples/log.py - :code: python +.. testcode:: + + # Enable the logging at application start, before connecting to the server. + import aerospike + + ## SETTING THE LOG HANDLER ## + + # Clears saved log handler and disable logging + aerospike.set_log_handler(None) + + # Set default log handler to print to the console + aerospike.set_log_handler() + + def log_callback(level, func, path, line, msg): + print("[{}] {}".format(func, msg)) + + # Set log handler to custom callback function (defined above) + aerospike.set_log_handler(log_callback) + + + ## SETTING THE LOG LEVEL ## + + # disables log handling + aerospike.set_log_level(aerospike.LOG_LEVEL_OFF) + + # Enables log handling and sets level to LOG_LEVEL_TRACE + aerospike.set_log_level(aerospike.LOG_LEVEL_TRACE) + + # Create a client and connect it to the cluster + # This line will print use log_callback to print logs with a log level of TRACE + config = { + "hosts": [ + ("127.0.0.1", 3000) + ] + } + client = aerospike.client(config) .. py:function:: set_log_handler(log_handler: Optional[Callable[[int, str, str, int, str], None]]) diff --git a/doc/aerospike_helpers.expressions.rst b/doc/aerospike_helpers.expressions.rst index 48eb097167..a1f99beb50 100644 --- a/doc/aerospike_helpers.expressions.rst +++ b/doc/aerospike_helpers.expressions.rst @@ -36,8 +36,64 @@ the command will filter the results. Example: -.. include:: examples/expressions/top.py - :code: python +.. testcode:: + + import aerospike + from aerospike_helpers import expressions as exp + import pprint + + # Connect to database + config = {"hosts": [("127.0.0.1", 3000)]} + client = aerospike.client(config) + + # Write player records to database + keys = [("test", "demo", i) for i in range(1, 5)] + records = [ + {'user': "Chief" , 'scores': [6, 12, 4, 21], 'kd': 1.2}, + {'user': "Arbiter", 'scores': [5, 10, 5, 8] , 'kd': 1.0}, + {'user': "Johnson", 'scores': [8, 17, 20, 5], 'kd': 0.9}, + {'user': "Regret" , 'scores': [4, 2, 3, 5] , 'kd': 0.3} + ] + for key, record in zip(keys, records): + client.put(key, record) + + # Example #1: Get players with a K/D ratio >= 1.0 + + kdGreaterThan1 = exp.GE(exp.FloatBin("kd"), 1.0).compile() + policy = {"expressions": kdGreaterThan1} + brs = client.batch_read(keys, policy=policy) + + # Pretty print records' bins + for br in brs.batch_records: + # error code for FILTERED_OUT = 27 + pprint.pprint(br.record[2] if br.result != 27 else None) + # {'user': 'Chief', 'scores': [6, 12, 4, 21], 'kd': 1.2} + # {'user': 'Arbiter', 'scores': [5, 10, 5, 8], 'kd': 1.0} + # None + # None + + # Example #2: Get player with scores higher than 20 + # By nesting expressions, we can create complicated filters + + # Get top score + getTopScore = exp.ListGetByRank( + None, + aerospike.LIST_RETURN_VALUE, + exp.ResultType.INTEGER, + -1, + exp.ListBin("scores") + ) + # ...then compare it + scoreHigherThan20 = exp.GE(getTopScore, 20).compile() + policy = {"expressions": scoreHigherThan20} + brs = client.batch_read(keys, policy=policy) + + for br in brs.batch_records: + pprint.pprint(br.record[2] if br.result != 27 else None) + # {'user': 'Chief', 'scores': [6, 12, 4, 21], 'kd': 1.2} + # None + # {'user': 'Johnson', 'scores': [8, 17, 20, 5], 'kd': 0.9} + # None Currently, Aerospike expressions are supported for: * Record commands diff --git a/doc/client.rst b/doc/client.rst index 491c7993e0..20b28db8df 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -447,8 +447,19 @@ Record Commands :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/remove_bin.py - :code: python + .. testcode:: + + # Insert record + bins = {"bin1": 0, "bin2": 1} + client.put(keyTuple, bins) + + # Remove bin1 + client.remove_bin(keyTuple, ['bin1']) + + # Only bin2 shold remain + (keyTuple, meta, bins) = client.get(keyTuple) + print(bins) + # {'bin2': 1} .. index:: single: Batched Commands @@ -492,8 +503,64 @@ Batched Commands :raises: A subclass of :exc:`~aerospike.exception.AerospikeError`. See note above :meth:`batch_write` for details. - .. include:: examples/batch_write.py - :code: python + .. testcode:: + + from aerospike_helpers.batch import records as br + from aerospike_helpers.operations import operations as op + + # Keys + # Only insert two records with the first and second key + keyTuples = [ + ('test', 'demo', 'Robert'), + ('test', 'demo', 'Daniel'), + ('test', 'demo', 'Patrick'), + ] + client.put(keyTuples[0], {'id': 100, 'balance': 400}) + client.put(keyTuples[1], {'id': 101, 'balance': 200}) + client.put(keyTuples[2], {'id': 102, 'balance': 300}) + + # Apply different operations to different keys + batchRecords = br.BatchRecords( + [ + # Remove Robert from system + br.Remove( + key = keyTuples[0], + ), + # Modify Daniel's ID and balance + br.Write( + key = keyTuples[1], + ops = [ + op.write("id", 200), + op.write("balance", 100), + op.read("id"), + ], + ), + # Read Patrick's ID + br.Read( + key = keyTuples[2], + ops=[ + op.read("id") + ], + policy=None + ), + ] + ) + + client.batch_write(batchRecords) + + # batch_write modifies its BatchRecords argument. + # Results for each BatchRecord will be set in the result, record, and in_doubt fields. + for batchRecord in batchRecords.batch_records: + print(batchRecord.result) + print(batchRecord.record) + # Note how written bins return None if their values aren't read + # And removed records have an empty bins dictionary + # 0 + # (('test', 'demo', 'Robert', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) + # 0 + # (('test', 'demo', 'Daniel', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': 200, 'balance': None}) + # 0 + # (('test', 'demo', 'Patrick', bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'id': 102}) .. note:: Requires server version >= 6.0.0. @@ -539,8 +606,38 @@ Batched Commands :raises: A subclass of :exc:`~aerospike.exception.AerospikeError`. See note above :meth:`batch_write` for details. - .. include:: examples/batch_operate.py - :code: python + .. testcode:: + + from aerospike_helpers.operations import operations as op + + # Insert 3 records + keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] + bins = [ + {"id": 100, "balance": 200}, + {"id": 101, "balance": 400}, + {"id": 102, "balance": 300} + ] + for key, bin in zip(keys, bins): + client.put(key, bin) + + # Increment ID by 100 and balance by 500 for all employees + ops = [ + op.increment("id", 100), + op.increment("balance", 500), + op.read("balance") + ] + + batchRecords = client.batch_operate(keys, ops) + print(batchRecords.result) + # 0 + + # Print each individual transaction's results + # and record if it was read from + for batchRecord in batchRecords.batch_records: + print(f"{batchRecord.result}: {batchRecord.record}") + # 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 700}) + # 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 900}) + # 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 800}) .. note:: Requires server version >= 6.0.0. @@ -558,8 +655,33 @@ Batched Commands :return: an instance of :class:`BatchRecords `. :raises: A subclass of :exc:`~aerospike.exception.AerospikeError`. See note above :meth:`batch_write` for details. - .. include:: examples/batch_apply.py - :code: python + .. testcode:: + + # Insert 3 records + keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] + bins = [ + {"id": 100, "balance": 200}, + {"id": 101, "balance": 400}, + {"id": 102, "balance": 300} + ] + for key, bin in zip(keys, bins): + client.put(key, bin) + + # Apply a user defined function (UDF) to a batch + # of records using batch_apply. + client.udf_put("batch_apply.lua") + + args = ["balance", 0.5, 100] + batchRecords = client.batch_apply(keys, "batch_apply", "tax", args) + + print(batchRecords.result) + # 0 + + for batchRecord in batchRecords.batch_records: + print(f"{batchRecord.result}: {batchRecord.record}") + # 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 0}) + # 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 100}) + # 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 50}) .. include:: examples/batch_apply.lua :code: lua @@ -578,8 +700,29 @@ Batched Commands :return: an instance of :class:`BatchRecords `. :raises: A subclass of :exc:`~aerospike.exception.AerospikeError`. See note above :meth:`batch_write` for details. - .. include:: examples/batch_remove.py - :code: python + .. testcode:: + + # Insert 3 records + keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] + bins = [ + {"id": 100, "balance": 200}, + {"id": 101, "balance": 400}, + {"id": 102, "balance": 300} + ] + for key, bin in zip(keys, bins): + client.put(key, bin) + + batchRecords = client.batch_remove(keys) + + # A result of 0 means success + print(batchRecords.result) + # 0 + for batchRecord in batchRecords.batch_records: + print(batchRecord.result) + print(batchRecord.record) + # 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) + # 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) + # 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) .. index:: single: String Operations @@ -1001,8 +1144,16 @@ Info Operations :param TypeExpression expression: the compiled expression. See expressions at :py:mod:`aerospike_helpers`. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/get_expression_base64.py - :code: python + .. testcode:: + + from aerospike_helpers import expressions as exp + + # Compile expression + expr = exp.Eq(exp.IntBin("bin1"), 6).compile() + + base64 = client.get_expression_base64(expr) + print(base64) + # kwGTUQKkYmluMQY= .. versionchanged:: 7.0.0 @@ -1036,8 +1187,26 @@ Info Operations .. note:: Requires Aerospike server version >= 3.12 - .. include:: examples/truncate.py - :code: python + .. testcode:: + + import time + + client.put(("test", "demo", "key1"), {"bin": 4}) + + time.sleep(1) + # Take threshold time + current_time = time.time() + time.sleep(1) + + client.put(("test", "demo", "key2"), {"bin": 5}) + + threshold_ns = int(current_time * 10**9) + # Remove all items in set `demo` created before threshold time + # Record using key1 should be removed + client.truncate('test', 'demo', threshold_ns) + + # Remove all items in namespace + # client.truncate('test', None, 0) .. index:: single: Index Operations @@ -1160,8 +1329,19 @@ Index Operations :param list ctx: Aerospike CDT context: generated by aerospike CDT ctx helper :mod:`aerospike_helpers`. :raises: a subclass of :exc:`~aerospike.exception.AerospikeError`. - .. include:: examples/get_cdtctx_base64.py - :code: python + .. testcode:: + + import aerospike + from aerospike_helpers import cdt_ctx + + config = {'hosts': [('127.0.0.1', 3000)]} + client = aerospike.client(config) + + ctxs = [cdt_ctx.cdt_ctx_list_index(0)] + ctxs_base64 = client.get_cdtctx_base64(ctxs) + print("Base64 encoding of ctxs:", ctxs_base64) + + client.close() .. versionchanged:: 7.1.1 diff --git a/doc/examples/batch_apply.py b/doc/examples/batch_apply.py deleted file mode 100644 index 5b9f8fe6b4..0000000000 --- a/doc/examples/batch_apply.py +++ /dev/null @@ -1,25 +0,0 @@ -# Insert 3 records -keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] -bins = [ - {"id": 100, "balance": 200}, - {"id": 101, "balance": 400}, - {"id": 102, "balance": 300} -] -for key, bin in zip(keys, bins): - client.put(key, bin) - -# Apply a user defined function (UDF) to a batch -# of records using batch_apply. -client.udf_put("batch_apply.lua") - -args = ["balance", 0.5, 100] -batchRecords = client.batch_apply(keys, "batch_apply", "tax", args) - -print(batchRecords.result) -# 0 - -for batchRecord in batchRecords.batch_records: - print(f"{batchRecord.result}: {batchRecord.record}") -# 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 0}) -# 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 100}) -# 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'SUCCESS': 50}) diff --git a/doc/examples/batch_operate.py b/doc/examples/batch_operate.py deleted file mode 100644 index 5c9fa0eca7..0000000000 --- a/doc/examples/batch_operate.py +++ /dev/null @@ -1,30 +0,0 @@ -from aerospike_helpers.operations import operations as op - -# Insert 3 records -keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] -bins = [ - {"id": 100, "balance": 200}, - {"id": 101, "balance": 400}, - {"id": 102, "balance": 300} -] -for key, bin in zip(keys, bins): - client.put(key, bin) - -# Increment ID by 100 and balance by 500 for all employees -ops = [ - op.increment("id", 100), - op.increment("balance", 500), - op.read("balance") -] - -batchRecords = client.batch_operate(keys, ops) -print(batchRecords.result) -# 0 - -# Print each individual transaction's results -# and record if it was read from -for batchRecord in batchRecords.batch_records: - print(f"{batchRecord.result}: {batchRecord.record}") -# 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 700}) -# 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 900}) -# 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': None, 'balance': 800}) diff --git a/doc/examples/batch_remove.py b/doc/examples/batch_remove.py deleted file mode 100644 index 14198b0897..0000000000 --- a/doc/examples/batch_remove.py +++ /dev/null @@ -1,21 +0,0 @@ -# Insert 3 records -keys = [("test", "demo", f"employee{i}") for i in range(1, 4)] -bins = [ - {"id": 100, "balance": 200}, - {"id": 101, "balance": 400}, - {"id": 102, "balance": 300} -] -for key, bin in zip(keys, bins): - client.put(key, bin) - -batchRecords = client.batch_remove(keys) - -# A result of 0 means success -print(batchRecords.result) -# 0 -for batchRecord in batchRecords.batch_records: - print(batchRecord.result) - print(batchRecord.record) -# 0: (('test', 'demo', 'employee1', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) -# 0: (('test', 'demo', 'employee2', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) -# 0: (('test', 'demo', 'employee3', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) diff --git a/doc/examples/batch_write.py b/doc/examples/batch_write.py deleted file mode 100644 index 12c1522d2d..0000000000 --- a/doc/examples/batch_write.py +++ /dev/null @@ -1,56 +0,0 @@ -from aerospike_helpers.batch import records as br -from aerospike_helpers.operations import operations as op - -# Keys -# Only insert two records with the first and second key -keyTuples = [ - ('test', 'demo', 'Robert'), - ('test', 'demo', 'Daniel'), - ('test', 'demo', 'Patrick'), -] -client.put(keyTuples[0], {'id': 100, 'balance': 400}) -client.put(keyTuples[1], {'id': 101, 'balance': 200}) -client.put(keyTuples[2], {'id': 102, 'balance': 300}) - -# Apply different operations to different keys -batchRecords = br.BatchRecords( - [ - # Remove Robert from system - br.Remove( - key = keyTuples[0], - ), - # Modify Daniel's ID and balance - br.Write( - key = keyTuples[1], - ops = [ - op.write("id", 200), - op.write("balance", 100), - op.read("id"), - ], - ), - # Read Patrick's ID - br.Read( - key = keyTuples[2], - ops=[ - op.read("id") - ], - policy=None - ), - ] -) - -client.batch_write(batchRecords) - -# batch_write modifies its BatchRecords argument. -# Results for each BatchRecord will be set in the result, record, and in_doubt fields. -for batchRecord in batchRecords.batch_records: - print(batchRecord.result) - print(batchRecord.record) -# Note how written bins return None if their values aren't read -# And removed records have an empty bins dictionary -# 0 -# (('test', 'demo', 'Robert', bytearray(b'...')), {'ttl': 4294967295, 'gen': 0}, {}) -# 0 -# (('test', 'demo', 'Daniel', bytearray(b'...')), {'ttl': 2592000, 'gen': 2}, {'id': 200, 'balance': None}) -# 0 -# (('test', 'demo', 'Patrick', bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'id': 102}) diff --git a/doc/examples/exists.py b/doc/examples/exists.py deleted file mode 100644 index 5f82f14be7..0000000000 --- a/doc/examples/exists.py +++ /dev/null @@ -1,12 +0,0 @@ -# Check non-existent record -(key, meta) = client.exists(keyTuple) - -print(key) # ('test', 'demo', 'key', bytearray(b'...')) -print(meta) # None - -# Check existing record -client.put(keyTuple, {'bin1': 4}) -(key, meta) = client.exists(keyTuple) - -print(key) # ('test', 'demo', 'key', bytearray(b'...')) -print(meta) # {'ttl': 2592000, 'gen': 1} diff --git a/doc/examples/expressions/top.py b/doc/examples/expressions/top.py deleted file mode 100644 index 60d1415918..0000000000 --- a/doc/examples/expressions/top.py +++ /dev/null @@ -1,56 +0,0 @@ -import aerospike -from aerospike_helpers import expressions as exp -import pprint - -# Connect to database -config = {"hosts": [("127.0.0.1", 3000)]} -client = aerospike.client(config) - -# Write player records to database -keys = [("test", "demo", i) for i in range(1, 5)] -records = [ - {'user': "Chief" , 'scores': [6, 12, 4, 21], 'kd': 1.2}, - {'user': "Arbiter", 'scores': [5, 10, 5, 8] , 'kd': 1.0}, - {'user': "Johnson", 'scores': [8, 17, 20, 5], 'kd': 0.9}, - {'user': "Regret" , 'scores': [4, 2, 3, 5] , 'kd': 0.3} - ] -for key, record in zip(keys, records): - client.put(key, record) - -# Example #1: Get players with a K/D ratio >= 1.0 - -kdGreaterThan1 = exp.GE(exp.FloatBin("kd"), 1.0).compile() -policy = {"expressions": kdGreaterThan1} -brs = client.batch_read(keys, policy=policy) - -# Pretty print records' bins -for br in brs.batch_records: - # error code for FILTERED_OUT = 27 - pprint.pprint(br.record[2] if br.result != 27 else None) -# {'user': 'Chief', 'scores': [6, 12, 4, 21], 'kd': 1.2} -# {'user': 'Arbiter', 'scores': [5, 10, 5, 8], 'kd': 1.0} -# None -# None - -# Example #2: Get player with scores higher than 20 -# By nesting expressions, we can create complicated filters - -# Get top score -getTopScore = exp.ListGetByRank( - None, - aerospike.LIST_RETURN_VALUE, - exp.ResultType.INTEGER, - -1, - exp.ListBin("scores") - ) -# ...then compare it -scoreHigherThan20 = exp.GE(getTopScore, 20).compile() -policy = {"expressions": scoreHigherThan20} -brs = client.batch_read(keys, policy=policy) - -for br in brs.batch_records: - pprint.pprint(br.record[2] if br.result != 27 else None) -# {'user': 'Chief', 'scores': [6, 12, 4, 21], 'kd': 1.2} -# None -# {'user': 'Johnson', 'scores': [8, 17, 20, 5], 'kd': 0.9} -# None diff --git a/doc/examples/get.py b/doc/examples/get.py deleted file mode 100644 index 313f6cddc6..0000000000 --- a/doc/examples/get.py +++ /dev/null @@ -1,14 +0,0 @@ -# Get nonexistent record -try: - client.get(keyTuple) -except ex.RecordNotFound as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - # Error: 127.0.0.1:3000 AEROSPIKE_ERR_RECORD_NOT_FOUND [2] - -# Get existing record -client.put(keyTuple, {'bin1': 4}) -(key, meta, bins) = client.get(keyTuple) - -print(key) # ('test', 'demo', None, bytearray(b'...')) -print(meta) # {'ttl': 2592000, 'gen': 1} -print(bins) # {'bin1': 4} diff --git a/doc/examples/get_cdtctx_base64.py b/doc/examples/get_cdtctx_base64.py deleted file mode 100644 index 0df1d7daec..0000000000 --- a/doc/examples/get_cdtctx_base64.py +++ /dev/null @@ -1,11 +0,0 @@ -import aerospike -from aerospike_helpers import cdt_ctx - -config = {'hosts': [('127.0.0.1', 3000)]} -client = aerospike.client(config) - -ctxs = [cdt_ctx.cdt_ctx_list_index(0)] -ctxs_base64 = client.get_cdtctx_base64(ctxs) -print("Base64 encoding of ctxs:", ctxs_base64) - -client.close() diff --git a/doc/examples/get_expression_base64.py b/doc/examples/get_expression_base64.py deleted file mode 100644 index e0c4663804..0000000000 --- a/doc/examples/get_expression_base64.py +++ /dev/null @@ -1,8 +0,0 @@ -from aerospike_helpers import expressions as exp - -# Compile expression -expr = exp.Eq(exp.IntBin("bin1"), 6).compile() - -base64 = client.get_expression_base64(expr) -print(base64) -# kwGTUQKkYmluMQY= diff --git a/doc/examples/keyordereddict.py b/doc/examples/keyordereddict.py deleted file mode 100644 index 7c8cb65d89..0000000000 --- a/doc/examples/keyordereddict.py +++ /dev/null @@ -1,46 +0,0 @@ -import aerospike -from aerospike_helpers.operations import map_operations as mop -from aerospike_helpers.operations import list_operations as lop -import aerospike_helpers.cdt_ctx as ctx -from aerospike import KeyOrderedDict - -config = { 'hosts': [ ("localhost", 3000), ] } -client = aerospike.client(config) -map_policy={'map_order': aerospike.MAP_KEY_VALUE_ORDERED} - -key = ("test", "demo", 100) -client.put(key, {'map_list': []}) - -map_ctx1 = ctx.cdt_ctx_list_index(0) -map_ctx2 = ctx.cdt_ctx_list_index(1) -map_ctx3 = ctx.cdt_ctx_list_index(2) - -my_dict1 = {'a': 1, 'b': 2, 'c': 3} -my_dict2 = {'d': 4, 'e': 5, 'f': 6} -my_dict3 = {'g': 7, 'h': 8, 'i': 9} - -ops = [ - lop.list_append_items('map_list', [my_dict1, my_dict2, my_dict3]), - mop.map_set_policy('map_list', map_policy, [map_ctx1]), - mop.map_set_policy('map_list', map_policy, [map_ctx2]), - mop.map_set_policy('map_list', map_policy, [map_ctx3]) - ] -client.operate(key, ops) - -_, _, res = client.get(key) -print(res) - -element = KeyOrderedDict({'f': 6, 'e': 5, 'd': 4}) # this will match my_dict2 because it will be converted to key ordered. - -ops = [ - lop.list_get_by_value('map_list', element, aerospike.LIST_RETURN_COUNT) - ] -_, _, res = client.operate(key, ops) -print(res) - -client.remove(key) -client.close() - -# EXPECTED OUTPUT: -# {'map_list': [{'a': 1, 'b': 2, 'c': 3}, {'d': 4, 'e': 5, 'f': 6}, {'g': 7, 'h': 8, 'i': 9}]} -# {'map_list': 1} diff --git a/doc/examples/log.py b/doc/examples/log.py deleted file mode 100644 index e52b5daceb..0000000000 --- a/doc/examples/log.py +++ /dev/null @@ -1,34 +0,0 @@ -# Enable the logging at application start, before connecting to the server. -import aerospike - -## SETTING THE LOG HANDLER ## - -# Clears saved log handler and disable logging -aerospike.set_log_handler(None) - -# Set default log handler to print to the console -aerospike.set_log_handler() - -def log_callback(level, func, path, line, msg): - print("[{}] {}".format(func, msg)) - -# Set log handler to custom callback function (defined above) -aerospike.set_log_handler(log_callback) - - -## SETTING THE LOG LEVEL ## - -# disables log handling -aerospike.set_log_level(aerospike.LOG_LEVEL_OFF) - -# Enables log handling and sets level to LOG_LEVEL_TRACE -aerospike.set_log_level(aerospike.LOG_LEVEL_TRACE) - -# Create a client and connect it to the cluster -# This line will print use log_callback to print logs with a log level of TRACE -config = { - "hosts": [ - ("127.0.0.1", 3000) - ] -} -client = aerospike.client(config) diff --git a/doc/examples/lua/lua.py b/doc/examples/lua/lua.py deleted file mode 100644 index caaa541b64..0000000000 --- a/doc/examples/lua/lua.py +++ /dev/null @@ -1,41 +0,0 @@ -import aerospike - -config = {'hosts': [('127.0.0.1', 3000)], - 'lua': {'system_path':'/usr/local/aerospike/lua/', - 'user_path':'./'}} -client = aerospike.client(config) -client.udf_put("example.lua") - -# Remove index if it already exists -from aerospike import exception as ex -try: - client.index_remove("test", "ageIndex") -except ex.IndexNotFound: - pass - -bins = [ - {"name": "Jeff", "age": 20}, - {"name": "Derek", "age": 24}, - {"name": "Derek", "age": 21}, - {"name": "Derek", "age": 29}, - {"name": "Jeff", "age": 29}, -] -keys = [("test", "users", f"user{i}") for i in range(len(bins))] -for key, recordBins in zip(keys, bins): - client.put(key, recordBins) - -client.index_integer_create("test", "users", "age", "ageIndex") - -query = client.query('test', 'users') -query.apply('example', 'group_count', ['name', 'age', 21]) -names = query.results() - -# we expect a dict (map) whose keys are names, each with a count value -print(names) -# One of the Jeffs is excluded because he is under 21 -# [{'Derek': 3, 'Jeff': 1}] - -# Cleanup -client.index_remove("test", "ageIndex") -client.batch_remove(keys) -client.close() diff --git a/doc/examples/operate.py b/doc/examples/operate.py deleted file mode 100644 index 8c43c805ab..0000000000 --- a/doc/examples/operate.py +++ /dev/null @@ -1,21 +0,0 @@ -from aerospike_helpers.operations import operations - -# Add name, update age, and return attributes -client.put(keyTuple, {'age': 25, 'career': 'delivery boy'}) -ops = [ - operations.increment("age", 1000), - operations.write("name", "J."), - operations.prepend("name", "Phillip "), - operations.append("name", " Fry"), - operations.read("name"), - operations.read("career"), - operations.read("age") -] -(key, meta, bins) = client.operate(key, ops) - -print(key) # ('test', 'demo', None, bytearray(b'...')) -# The generation should only increment once -# A transaction is *atomic* -print(meta) # {'ttl': 2592000, 'gen': 2} -print(bins) # Will display all bins selected by read operations -# {'name': 'Phillip J. Fry', 'career': 'delivery boy', 'age': 1025} diff --git a/doc/examples/operate_ordered.py b/doc/examples/operate_ordered.py deleted file mode 100644 index 1f4536d478..0000000000 --- a/doc/examples/operate_ordered.py +++ /dev/null @@ -1,19 +0,0 @@ -from aerospike_helpers.operations import operations - -# Add name, update age, and return attributes -client.put(keyTuple, {'age': 25, 'career': 'delivery boy'}) -ops = [ - operations.increment("age", 1000), - operations.write("name", "J."), - operations.prepend("name", "Phillip "), - operations.append("name", " Fry"), - operations.read("name"), - operations.read("career"), - operations.read("age") -] -(key, meta, bins) = client.operate_ordered(keyTuple, ops) - -# Same output for key and meta as operate() -# But read operations are outputted as bin-value pairs -print(bins) -# [('name': 'Phillip J. Fry'), ('career': 'delivery boy'), ('age': 1025)] diff --git a/doc/examples/put.py b/doc/examples/put.py deleted file mode 100644 index 017e102fd6..0000000000 --- a/doc/examples/put.py +++ /dev/null @@ -1,11 +0,0 @@ -# Insert a record with bin1 -client.put(keyTuple, {'bin1': 4}) - -# Insert another bin named bin2 -client.put(keyTuple, {'bin2': "value"}) - -# Remove bin1 from this record -client.put(keyTuple, {'bin2': aerospike.null()}) - -# Removing the last bin should delete this record -client.put(keyTuple, {'bin1': aerospike.null()}) diff --git a/doc/examples/query/boilerplate.py b/doc/examples/query/boilerplate.py deleted file mode 100644 index fb5503a050..0000000000 --- a/doc/examples/query/boilerplate.py +++ /dev/null @@ -1,38 +0,0 @@ -import aerospike -import sys -from aerospike import exception as ex - -config = {'hosts': [('127.0.0.1', 3000)]} - -# Create a client and connect it to the cluster -try: - client = aerospike.client(config) - client.truncate('test', "demo", 0) -except ex.ClientError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - sys.exit(1) - -# Remove old indices -try: - client.index_remove("test", "scoreIndex") - client.index_remove("test", "eloIndex") -except ex.AerospikeError as e: - # Ignore if no indices found - pass - -# Insert 4 records -keyTuples = [("test", "demo", f"player{i}") for i in range(4)] -bins = [ - {"score": 100, "elo": 1400}, - {"score": 20, "elo": 1500}, - {"score": 10, "elo": 1100}, - {"score": 200, "elo": 900} -] -for keyTuple, bin in zip(keyTuples, bins): - client.put(keyTuple, bin) - -query = client.query('test', 'demo') - -# Queries require a secondary index for each bin name -client.index_integer_create("test", "demo", "score", "scoreIndex") -client.index_integer_create("test", "demo", "elo", "eloIndex") diff --git a/doc/examples/query/foreach.py b/doc/examples/query/foreach.py deleted file mode 100644 index a73bf4044c..0000000000 --- a/doc/examples/query/foreach.py +++ /dev/null @@ -1,19 +0,0 @@ -# Callback function -# Calculates new elo for a player -def updateElo(record): - keyTuple, _, bins = record - # Add score to elo - bins["elo"] = bins["elo"] + bins["score"] - client.put(keyTuple, bins) - -query.foreach(updateElo) - -# Player elos should be updated -brs = client.batch_read(keyTuples) -for br in brs.batch_records: - # Print record bin - print(br.record[2]) -# {'score': 100, 'elo': 1500} -# {'score': 20, 'elo': 1520} -# {'score': 10, 'elo': 1110} -# {'score': 200, 'elo': 1100} diff --git a/doc/examples/query/foreachfalse.py b/doc/examples/query/foreachfalse.py deleted file mode 100644 index 9b824b1566..0000000000 --- a/doc/examples/query/foreachfalse.py +++ /dev/null @@ -1,20 +0,0 @@ -# Adds record keys from a stream to a list -# But limits the number of keys to "lim" -def limit(lim: int, result: list): - # Integers are immutable - # so a list (mutable) is used for the counter - c = [0] - def key_add(record): - key, metadata, bins = record - if c[0] < lim: - result.append(key) - c[0] = c[0] + 1 - else: - return False - return key_add - -from aerospike import predicates as p - -keys = [] -query.foreach(limit(2, keys)) -print(len(keys)) # 2 diff --git a/doc/examples/query/results.py b/doc/examples/query/results.py deleted file mode 100644 index 24e9c149e9..0000000000 --- a/doc/examples/query/results.py +++ /dev/null @@ -1,9 +0,0 @@ -from aerospike import predicates - -query.select('score') -query.where(predicates.equals('score', 100)) - -records = query.results() -# Matches one record -print(records) -# [(('test', 'demo', None, bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'score': 100})] diff --git a/doc/examples/remove.py b/doc/examples/remove.py deleted file mode 100644 index f4e94f7465..0000000000 --- a/doc/examples/remove.py +++ /dev/null @@ -1,12 +0,0 @@ -# Insert a record -client.put(keyTuple, {"bin1": 4}) - -# Try to remove it with the wrong generation -try: - client.remove(keyTuple, meta={'gen': 5}, policy={'gen': aerospike.POLICY_GEN_EQ}) -except ex.AerospikeError as e: - # Error: AEROSPIKE_ERR_RECORD_GENERATION [3] - print("Error: {0} [{1}]".format(e.msg, e.code)) - -# Remove it ignoring generation -client.remove(keyTuple) diff --git a/doc/examples/remove_bin.py b/doc/examples/remove_bin.py deleted file mode 100644 index 8619f3c1b4..0000000000 --- a/doc/examples/remove_bin.py +++ /dev/null @@ -1,11 +0,0 @@ -# Insert record -bins = {"bin1": 0, "bin2": 1} -client.put(keyTuple, bins) - -# Remove bin1 -client.remove_bin(keyTuple, ['bin1']) - -# Only bin2 shold remain -(keyTuple, meta, bins) = client.get(keyTuple) -print(bins) -# {'bin2': 1} diff --git a/doc/examples/scan/top.py b/doc/examples/scan/top.py deleted file mode 100644 index 1923a7df32..0000000000 --- a/doc/examples/scan/top.py +++ /dev/null @@ -1,46 +0,0 @@ -import aerospike -from aerospike import exception as ex -import sys -import time - -config = {"hosts": [("127.0.0.1", 3000)]} -client = aerospike.client(config) - -# register udf -try: - client.udf_put("./my_udf.lua") -except ex.AerospikeError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - client.close() - sys.exit(1) - -# put records and apply udf -try: - keys = [("test", "demo", 1), ("test", "demo", 2), ("test", "demo", 3)] - records = [{"number": 1}, {"number": 2}, {"number": 3}] - for i in range(3): - client.put(keys[i], records[i]) - - scan = client.scan("test", "demo") - scan.apply("my_udf", "my_udf", ["number", 10]) - job_id = scan.execute_background() - - # wait for job to finish - while True: - response = client.job_info(job_id, aerospike.JOB_SCAN) - if response["status"] != aerospike.JOB_STATUS_INPROGRESS: - break - time.sleep(0.25) - - brs = client.batch_read(keys) - for br in brs.batch_records: - print(br.record[2]) -except ex.AerospikeError as e: - print("Error: {0} [{1}]".format(e.msg, e.code)) - sys.exit(1) -finally: - client.close() -# EXPECTED OUTPUT: -# {'number': 11} -# {'number': 12} -# {'number': 13} diff --git a/doc/examples/select.py b/doc/examples/select.py deleted file mode 100644 index 9ec98d0a39..0000000000 --- a/doc/examples/select.py +++ /dev/null @@ -1,18 +0,0 @@ -# Record to select from -client.put(keyTuple, {'bin1': 4, 'bin2': 3}) - -# Only get bin1 -(key, meta, bins) = client.select(keyTuple, ['bin1']) - -# Similar output to get() -print(key) # ('test', 'demo', 'key', bytearray(b'...')) -print(meta) # {'ttl': 2592000, 'gen': 1} -print(bins) # {'bin1': 4} - -# Get all bins -(key, meta, bins) = client.select(keyTuple, ['bin1', 'bin2']) -print(bins) # {'bin1': 4, 'bin2': 3} - -# Get nonexistent bin -(key, meta, bins) = client.select(keyTuple, ['bin3']) -print(bins) # {} diff --git a/doc/examples/serializer.py b/doc/examples/serializer.py deleted file mode 100644 index 5c08b711f7..0000000000 --- a/doc/examples/serializer.py +++ /dev/null @@ -1,76 +0,0 @@ -import aerospike -import marshal -import json - -# Serializers and deserializers -# Both local and global serializers use json library -# Functions print which one is being used - -def classSerializer(obj): - print("Using class serializer") - return json.dumps(obj) - -def classDeserializer(bytes): - print("Using class deserializer") - return json.loads(bytes) - -def localSerializer(obj): - print("Using local serializer") - return json.dumps(obj) - -def localDeserializer(bytes): - print("Using local deserializer") - return json.loads(bytes) - -# First client has class-level serializer set in aerospike module -aerospike.set_serializer(classSerializer) -aerospike.set_deserializer(classDeserializer) -config = { - 'hosts': [('127.0.0.1', 3000)] -} -client = aerospike.client(config) - -# Second client has instance-level serializer set in client config -config['serialization'] = (localSerializer, localDeserializer) -client2 = aerospike.client(config) - -# Keys: foo1, foo2, foo3 -keys = [('test', 'demo', f'foo{i}') for i in range(1, 4)] -# Tuple is an unsupported type -tupleBin = {'bin': (1, 2, 3)} - -# Use the default, built-in serialization (cPickle) -client.put(keys[0], tupleBin) -(_, _, bins) = client.get(keys[0]) -print(bins) - -# Expected output: -# {'bin': (1, 2, 3)} - -# First client uses class-level, user-defined serialization -# No instance-level serializer was declared -client.put(keys[1], tupleBin, serializer=aerospike.SERIALIZER_USER) -(_, _, bins) = client.get(keys[1]) -# Notice that json-encoding a tuple produces a list -print(bins) - -# Expected output: -# Using class serializer -# Using class deserializer -# {'bin': [1, 2, 3]} - -# Second client uses instance-level, user-defined serialization -# Instance-level serializer overrides class-level serializer -client2.put(keys[2], tupleBin, serializer=aerospike.SERIALIZER_USER) -(_, _, bins) = client2.get(keys[2]) -print(bins) - -# Expected output: -# Using local serializer -# Using local deserializer -# {'bin': [1, 2, 3]} - -# Cleanup -client.batch_remove(keys) -client.close() -client2.close() diff --git a/doc/examples/touch.py b/doc/examples/touch.py deleted file mode 100644 index 1b9021c1a7..0000000000 --- a/doc/examples/touch.py +++ /dev/null @@ -1,12 +0,0 @@ -# Insert record and get its metadata -client.put(keyTuple, bins = {"bin1": 4}) -(key, meta) = client.exists(keyTuple) -print(meta) # {'ttl': 2592000, 'gen': 1} - -# Explicitly set TTL to 120 -# and increment generation -client.touch(keyTuple, 120) - -# Record metadata should be updated -(key, meta) = client.exists(keyTuple) -print(meta) # {'ttl': 120, 'gen': 2} diff --git a/doc/examples/truncate.py b/doc/examples/truncate.py deleted file mode 100644 index c1b7d7f356..0000000000 --- a/doc/examples/truncate.py +++ /dev/null @@ -1,18 +0,0 @@ -import time - -client.put(("test", "demo", "key1"), {"bin": 4}) - -time.sleep(1) -# Take threshold time -current_time = time.time() -time.sleep(1) - -client.put(("test", "demo", "key2"), {"bin": 5}) - -threshold_ns = int(current_time * 10**9) -# Remove all items in set `demo` created before threshold time -# Record using key1 should be removed -client.truncate('test', 'demo', threshold_ns) - -# Remove all items in namespace -# client.truncate('test', None, 0) diff --git a/doc/key_ordered_dict.rst b/doc/key_ordered_dict.rst index 0c30a51671..5edcaae850 100644 --- a/doc/key_ordered_dict.rst +++ b/doc/key_ordered_dict.rst @@ -11,8 +11,54 @@ The KeyOrderedDict class is a dictionary that directly maps to a key ordered map on the Aerospike server. This assists in matching key ordered maps through various read operations. See the example snippet below. -.. include:: examples/keyordereddict.py - :code: python +.. testcode:: + + import aerospike + from aerospike_helpers.operations import map_operations as mop + from aerospike_helpers.operations import list_operations as lop + import aerospike_helpers.cdt_ctx as ctx + from aerospike import KeyOrderedDict + + config = { 'hosts': [ ("localhost", 3000), ] } + client = aerospike.client(config) + map_policy={'map_order': aerospike.MAP_KEY_VALUE_ORDERED} + + key = ("test", "demo", 100) + client.put(key, {'map_list': []}) + + map_ctx1 = ctx.cdt_ctx_list_index(0) + map_ctx2 = ctx.cdt_ctx_list_index(1) + map_ctx3 = ctx.cdt_ctx_list_index(2) + + my_dict1 = {'a': 1, 'b': 2, 'c': 3} + my_dict2 = {'d': 4, 'e': 5, 'f': 6} + my_dict3 = {'g': 7, 'h': 8, 'i': 9} + + ops = [ + lop.list_append_items('map_list', [my_dict1, my_dict2, my_dict3]), + mop.map_set_policy('map_list', map_policy, [map_ctx1]), + mop.map_set_policy('map_list', map_policy, [map_ctx2]), + mop.map_set_policy('map_list', map_policy, [map_ctx3]) + ] + client.operate(key, ops) + + _, _, res = client.get(key) + print(res) + + element = KeyOrderedDict({'f': 6, 'e': 5, 'd': 4}) # this will match my_dict2 because it will be converted to key ordered. + + ops = [ + lop.list_get_by_value('map_list', element, aerospike.LIST_RETURN_COUNT) + ] + _, _, res = client.operate(key, ops) + print(res) + + client.remove(key) + client.close() + + # EXPECTED OUTPUT: + # {'map_list': [{'a': 1, 'b': 2, 'c': 3}, {'d': 4, 'e': 5, 'f': 6}, {'g': 7, 'h': 8, 'i': 9}]} + # {'map_list': 1} KeyOrderedDict inherits from :class:`dict` and has no extra functionality. The only difference is its mapping to a key ordered map. diff --git a/doc/query.rst b/doc/query.rst index b03998f96d..cd8aa39566 100755 --- a/doc/query.rst +++ b/doc/query.rst @@ -111,8 +111,87 @@ Methods Assume this boilerplate code is run before all examples below: -.. include:: examples/query/boilerplate.py - :code: python +.. testsetup:: + + import aerospike + import sys + from aerospike import exception as ex + + config = {'hosts': [('127.0.0.1', 3000)]} + + # Create a client and connect it to the cluster + try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) + except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + + # Remove old indices + try: + client.index_remove("test", "scoreIndex") + client.index_remove("test", "eloIndex") + except ex.AerospikeError as e: + # Ignore if no indices found + pass + + # Insert 4 records + keyTuples = [("test", "demo", f"player{i}") for i in range(4)] + bins = [ + {"score": 100, "elo": 1400}, + {"score": 20, "elo": 1500}, + {"score": 10, "elo": 1100}, + {"score": 200, "elo": 900} + ] + for keyTuple, bin in zip(keyTuples, bins): + client.put(keyTuple, bin) + + query = client.query('test', 'demo') + + # Queries require a secondary index for each bin name + client.index_integer_create("test", "demo", "score", "scoreIndex") + client.index_integer_create("test", "demo", "elo", "eloIndex") + +.. code-block:: Python + + import aerospike + import sys + from aerospike import exception as ex + + config = {'hosts': [('127.0.0.1', 3000)]} + + # Create a client and connect it to the cluster + try: + client = aerospike.client(config) + client.truncate('test', "demo", 0) + except ex.ClientError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + + # Remove old indices + try: + client.index_remove("test", "scoreIndex") + client.index_remove("test", "eloIndex") + except ex.AerospikeError as e: + # Ignore if no indices found + pass + + # Insert 4 records + keyTuples = [("test", "demo", f"player{i}") for i in range(4)] + bins = [ + {"score": 100, "elo": 1400}, + {"score": 20, "elo": 1500}, + {"score": 10, "elo": 1100}, + {"score": 200, "elo": 900} + ] + for keyTuple, bin in zip(keyTuples, bins): + client.put(keyTuple, bin) + + query = client.query('test', 'demo') + + # Queries require a secondary index for each bin name + client.index_integer_create("test", "demo", "score", "scoreIndex") + client.index_integer_create("test", "demo", "elo", "eloIndex") .. class:: Query :noindex: @@ -177,8 +256,17 @@ Assume this boilerplate code is run before all examples below: :param dict options: optional :ref:`aerospike_query_options`. :return: a :class:`list` of :ref:`aerospike_record_tuple`. - .. include:: examples/query/results.py - :code: python + .. testcode:: + + from aerospike import predicates + + query.select('score') + query.where(predicates.equals('score', 100)) + + records = query.results() + # Matches one record + print(records) + # [(('test', 'demo', None, bytearray(b'...')), {'ttl': 2592000, 'gen': 1}, {'score': 100})] .. note:: As of client 7.0.0 and with server >= 6.0 results and the query policy "partition_filter" see :ref:`aerospike_partition_objects` can be used to specify which partitions/records @@ -217,13 +305,52 @@ Assume this boilerplate code is run before all examples below: :param dict policy: optional :ref:`aerospike_query_policies`. :param dict options: optional :ref:`aerospike_query_options`. - .. include:: examples/query/foreach.py - :code: python + .. testcode:: + + # Callback function + # Calculates new elo for a player + def updateElo(record): + keyTuple, _, bins = record + # Add score to elo + bins["elo"] = bins["elo"] + bins["score"] + client.put(keyTuple, bins) + + query.foreach(updateElo) + + # Player elos should be updated + brs = client.batch_read(keyTuples) + for br in brs.batch_records: + # Print record bin + print(br.record[2]) + # {'score': 100, 'elo': 1500} + # {'score': 20, 'elo': 1520} + # {'score': 10, 'elo': 1110} + # {'score': 200, 'elo': 1100} .. note:: To stop the stream return ``False`` from the callback function. - .. include:: examples/query/foreachfalse.py - :code: python + .. testcode:: + + # Adds record keys from a stream to a list + # But limits the number of keys to "lim" + def limit(lim: int, result: list): + # Integers are immutable + # so a list (mutable) is used for the counter + c = [0] + def key_add(record): + key, metadata, bins = record + if c[0] < lim: + result.append(key) + c[0] = c[0] + 1 + else: + return False + return key_add + + from aerospike import predicates as p + + keys = [] + query.foreach(limit(2, keys)) + print(len(keys)) # 2 .. note:: As of client 7.0.0 and with server >= 6.0 foreach and the query policy "partition_filter" see :ref:`aerospike_partition_objects` can be used to specify which partitions/records @@ -284,8 +411,49 @@ Assume this boilerplate code is run before all examples below: Assume the example code above is in a file called "example.lua", and is the same folder as the following script. - .. include:: examples/lua/lua.py - :code: python + .. testcode:: + + import aerospike + + config = {'hosts': [('127.0.0.1', 3000)], + 'lua': {'system_path':'/usr/local/aerospike/lua/', + 'user_path':'./'}} + client = aerospike.client(config) + client.udf_put("example.lua") + + # Remove index if it already exists + from aerospike import exception as ex + try: + client.index_remove("test", "ageIndex") + except ex.IndexNotFound: + pass + + bins = [ + {"name": "Jeff", "age": 20}, + {"name": "Derek", "age": 24}, + {"name": "Derek", "age": 21}, + {"name": "Derek", "age": 29}, + {"name": "Jeff", "age": 29}, + ] + keys = [("test", "users", f"user{i}") for i in range(len(bins))] + for key, recordBins in zip(keys, bins): + client.put(key, recordBins) + + client.index_integer_create("test", "users", "age", "ageIndex") + + query = client.query('test', 'users') + query.apply('example', 'group_count', ['name', 'age', 21]) + names = query.results() + + # we expect a dict (map) whose keys are names, each with a count value + print(names) + # One of the Jeffs is excluded because he is under 21 + # [{'Derek': 3, 'Jeff': 1}] + + # Cleanup + client.index_remove("test", "ageIndex") + client.batch_remove(keys) + client.close() With stream UDFs, the final reduce steps (which ties the results from the reducers of the cluster nodes) executes on the diff --git a/doc/scan.rst b/doc/scan.rst index 258172fc31..fd46d96a83 100755 --- a/doc/scan.rst +++ b/doc/scan.rst @@ -303,8 +303,54 @@ Methods .. note:: Python client version 3.10.0 implemented scan execute_background. - .. include:: examples/scan/top.py - :code: python + .. testcode:: + + import aerospike + from aerospike import exception as ex + import sys + import time + + config = {"hosts": [("127.0.0.1", 3000)]} + client = aerospike.client(config) + + # register udf + try: + client.udf_put("./my_udf.lua") + except ex.AerospikeError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + client.close() + sys.exit(1) + + # put records and apply udf + try: + keys = [("test", "demo", 1), ("test", "demo", 2), ("test", "demo", 3)] + records = [{"number": 1}, {"number": 2}, {"number": 3}] + for i in range(3): + client.put(keys[i], records[i]) + + scan = client.scan("test", "demo") + scan.apply("my_udf", "my_udf", ["number", 10]) + job_id = scan.execute_background() + + # wait for job to finish + while True: + response = client.job_info(job_id, aerospike.JOB_SCAN) + if response["status"] != aerospike.JOB_STATUS_INPROGRESS: + break + time.sleep(0.25) + + brs = client.batch_read(keys) + for br in brs.batch_records: + print(br.record[2]) + except ex.AerospikeError as e: + print("Error: {0} [{1}]".format(e.msg, e.code)) + sys.exit(1) + finally: + client.close() + # EXPECTED OUTPUT: + # {'number': 11} + # {'number': 12} + # {'number': 13} .. include:: examples/scan/my_udf.lua :code: lua From a550a6e87ca682313709e15bf6f2a9e56f72a775 Mon Sep 17 00:00:00 2001 From: Julian Nguyen <109386615+juliannguyen4@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:15:16 -0700 Subject: [PATCH 124/124] Fix testcode naming --- doc/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/client.rst b/doc/client.rst index 20b28db8df..25d0c582ab 100755 --- a/doc/client.rst +++ b/doc/client.rst @@ -206,7 +206,7 @@ Record Commands print(key) print(meta) - .. testblock:: + .. testcode:: ('test', 'demo', 'key', bytearray(b'...')) None