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 01/72] 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 02/72] 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 03/72] 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 04/72] 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 05/72] *.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 06/72] 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 07/72] 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 08/72] 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 09/72] 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 10/72] 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 11/72] 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 12/72] 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 13/72] 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 14/72] 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 15/72] 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 16/72] 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 17/72] 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 18/72] 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 19/72] 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 20/72] 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 21/72] 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 22/72] 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 23/72] 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 24/72] 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 25/72] 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 26/72] 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 27/72] 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 28/72] 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 29/72] 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 30/72] 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 31/72] 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 32/72] 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 33/72] 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 34/72] 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 35/72] 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 36/72] 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 37/72] 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 38/72] 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 39/72] 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 40/72] 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 41/72] 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 42/72] 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 43/72] 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 44/72] 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 45/72] 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 46/72] 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 47/72] 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 48/72] 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 49/72] 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 50/72] 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 51/72] 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 52/72] 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 53/72] 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 54/72] 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 55/72] 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 56/72] 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 57/72] 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 58/72] 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 59/72] 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 60/72] 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 61/72] 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 62/72] 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 63/72] 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 64/72] 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 65/72] 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 66/72] 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 67/72] 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 68/72] 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 69/72] 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 70/72] 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 71/72] 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 72/72] 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