├── .coveragerc ├── .github ├── dependabot.yml └── workflows │ ├── doc-update.yml │ ├── release.yml │ ├── run-examples-with-release.yaml │ ├── run-examples.yaml │ ├── trivy-scan.yml │ ├── validate-nslookup.yml │ └── validate.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE.txt ├── Makefile ├── README.md ├── SECURITY.md ├── THIRD_PARTY_LICENSES.txt ├── bin └── docker-utils.sh ├── docs ├── Makefile ├── aggregation.rst ├── api_reference.rst ├── api_reference │ ├── aggregator.rst │ ├── ai.rst │ ├── comparator.rst │ ├── event.rst │ ├── extractor.rst │ ├── filter.rst │ └── processor.rst ├── basics.rst ├── caches.rst ├── conf.py ├── entryprocessing.rst ├── events.rst ├── index.rst ├── installation.rst ├── make.bat ├── querying.rst ├── serialization.rst └── sessions.rst ├── etc └── proto │ ├── cache_service_messages_v1.proto │ ├── common_messages_v1.proto │ ├── messages.proto │ ├── proxy_service_messages_v1.proto │ ├── proxy_service_v1.proto │ └── services.proto ├── examples ├── README.md ├── aggregators.py ├── basics.py ├── events.py ├── filters.py ├── movies.json.gzip ├── near_caching.py ├── processors.py ├── python_object_keys_and_values.py └── vector_search.py ├── pyproject.toml ├── sbom_generation.yaml ├── setup.cfg ├── setup.py ├── sonar-project.properties ├── src ├── __init__.py └── coherence │ ├── __init__.py │ ├── aggregator.py │ ├── ai.py │ ├── cache_service_messages_v1_pb2.py │ ├── cache_service_messages_v1_pb2.pyi │ ├── cache_service_messages_v1_pb2_grpc.py │ ├── client.py │ ├── common_messages_v1_pb2.py │ ├── common_messages_v1_pb2.pyi │ ├── common_messages_v1_pb2_grpc.py │ ├── comparator.py │ ├── entry.py │ ├── event.py │ ├── extractor.py │ ├── filter.py │ ├── local_cache.py │ ├── messages_pb2.py │ ├── messages_pb2.pyi │ ├── messages_pb2_grpc.py │ ├── nslookup.py │ ├── processor.py │ ├── proxy_service_messages_v1_pb2.py │ ├── proxy_service_messages_v1_pb2.pyi │ ├── proxy_service_messages_v1_pb2_grpc.py │ ├── proxy_service_v1_pb2.py │ ├── proxy_service_v1_pb2.pyi │ ├── proxy_service_v1_pb2_grpc.py │ ├── serialization.py │ ├── services_pb2.py │ ├── services_pb2.pyi │ ├── services_pb2_grpc.py │ └── util.py └── tests ├── __init__.py ├── address.py ├── conftest.py ├── e2e ├── __init__.py ├── test_aggregators.py ├── test_ai.py ├── test_client.py ├── test_events.py ├── test_filters.py ├── test_near_caching.py ├── test_processors.py └── test_session.py ├── java ├── coherence-python-client-data-jakarta │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── oracle │ │ └── coherence │ │ └── python │ │ └── testing │ │ ├── Address.java │ │ └── Customer.java ├── coherence-python-client-data-javax │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── oracle │ │ └── coherence │ │ └── python │ │ └── testing │ │ ├── Address.java │ │ └── Customer.java ├── coherence-python-test │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── oracle │ │ │ └── coherence │ │ │ └── python │ │ │ └── testing │ │ │ ├── LongRunningProcessor.java │ │ │ ├── RestServer.java │ │ │ ├── SimpleCacheLoader.java │ │ │ └── package-info.java │ │ └── resources │ │ ├── META-INF │ │ └── type-aliases.properties │ │ └── test-cache-config.xml └── pom.xml ├── logging.conf ├── person.py ├── scripts ├── keys.sh ├── run-tests.sh └── startup-clusters.sh ├── unit ├── __init__.py ├── test_cache_options.py ├── test_environment.py ├── test_extractors.py ├── test_local_cache.py └── test_serialization.py └── utils └── docker-compose-2-members.yaml /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | src/coherence/messages_pb2.py 4 | src/coherence/messages_pb2_grpc.py 5 | src/coherence/services_pb2.py 6 | src/coherence/services_pb2_grpc.py 7 | src/coherence/cache_service_messages_v1_pb2.py 8 | src/coherence/cache_service_messages_v1_pb2_grpc.py 9 | src/coherence/common_messages_v1_pb2.py 10 | src/coherence/common_messages_v1_pb2_grpc.py 11 | src/coherence/proxy_service_messages_v1_pb2.py 12 | src/coherence/proxy_service_messages_v1_pb2_grpc.py 13 | src/coherence/proxy_service_v1_pb2.py 14 | src/coherence/proxy_service_v1_pb2_grpc.py 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "maven" 8 | directory: "/tests/java/" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "pip" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/doc-update.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025, Oracle Corporation and/or its affiliates. All rights reserved. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | # --------------------------------------------------------------------------- 6 | # Coherence Python Client GitHub Documentation update Actions 7 | # --------------------------------------------------------------------------- 8 | 9 | 10 | name: Update Documentation for the project 11 | 12 | on: 13 | # Always triggered manually 14 | workflow_dispatch: 15 | 16 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 17 | permissions: 18 | contents: write 19 | pages: write 20 | id-token: write 21 | 22 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 23 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 24 | concurrency: 25 | group: "pages" 26 | cancel-in-progress: false 27 | 28 | jobs: 29 | build: 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-python@v5 35 | with: 36 | python-version: "3.9.x" 37 | 38 | - name: Install Poetry 39 | shell: bash 40 | run: | 41 | pip install poetry=="1.8.4" 42 | 43 | - name: Install Dependencies 44 | run: python -m poetry install 45 | 46 | - name: Generate docs 47 | shell: bash 48 | run: | 49 | python -m poetry run make docs 50 | 51 | - name: Setup Pages 52 | id: pages 53 | uses: actions/configure-pages@v5 54 | 55 | - name: Upload artifact 56 | uses: actions/upload-pages-artifact@v3 57 | with: 58 | name: "github-pages" 59 | path: ./docs/_build 60 | 61 | # - name: Download artifact 62 | # uses: actions/download-artifact@v4 63 | # with: 64 | # name: github-pages 65 | # path: . 66 | # 67 | # - name: Get latest release version 68 | # id: get-version 69 | # run: | 70 | # version=$(git describe --tags $(git rev-list --tags --max-count=1)) 71 | # echo "get_released_ver=$version" >> "$GITHUB_ENV" 72 | # 73 | # - name: Print latest release version 74 | # run: | 75 | # echo "${{ env.get_released_ver }}" 76 | # 77 | # - name: Attach doc zip to release 78 | # uses: actions/upload-release-asset@v1 79 | # env: 80 | # GITHUB_TOKEN: ${{ github.token }} 81 | # with: 82 | # upload_url: ${{ github.event.release.upload_url }} 83 | # asset_path: ./artifact.tar 84 | # asset_name: ${{ format('coherence_client-{0}-doc.tar', env.get_released_ver) }} 85 | # asset_content_type: application/gzip 86 | 87 | # Deploy job 88 | deploy: 89 | # Add a dependency to the build job 90 | needs: build 91 | 92 | # Deploy to the github-pages environment 93 | environment: 94 | name: github-pages 95 | url: ${{ steps.deployment.outputs.page_url }} 96 | 97 | # Specify runner + deployment step 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Deploy to GitHub Pages 101 | id: deployment 102 | uses: actions/deploy-pages@v4 # or the latest "vX.X.X" version tag for this action 103 | with: 104 | artifact_name: github-pages 105 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023, Oracle Corporation and/or its affiliates. All rights reserved. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | # --------------------------------------------------------------------------- 6 | # Coherence Python Client GitHub Release Actions build. 7 | # --------------------------------------------------------------------------- 8 | 9 | 10 | name: Publish on Pypi and GitHub Pages 11 | 12 | on: 13 | release: 14 | # This specifies that the build will be triggered when we publish a release 15 | types: [published] 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: write 20 | pages: write 21 | id-token: write 22 | 23 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 24 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 25 | concurrency: 26 | group: "pages" 27 | cancel-in-progress: false 28 | 29 | jobs: 30 | build: 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: actions/setup-python@v5 36 | with: 37 | python-version: "3.9.x" 38 | 39 | - name: Install Poetry 40 | shell: bash 41 | run: | 42 | pip install poetry=="1.8.4" 43 | 44 | - name: Install Dependencies 45 | run: python -m poetry install 46 | 47 | - name: Generate docs 48 | shell: bash 49 | run: | 50 | python -m poetry run make docs 51 | 52 | - name: Setup Pages 53 | id: pages 54 | uses: actions/configure-pages@v5 55 | 56 | - name: Upload artifact 57 | uses: actions/upload-pages-artifact@v3 58 | with: 59 | name: "github-pages" 60 | path: ./docs/_build 61 | 62 | - name: Download artifact 63 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 64 | with: 65 | name: github-pages 66 | path: . 67 | 68 | - name: Publish to PyPi 69 | shell: bash 70 | env: 71 | TWINE_USERNAME: ${{ secrets.PYPI_USER }} 72 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 73 | run: | 74 | pip install --upgrade pip 75 | pip install build twine 76 | python3 -m build 77 | ls -la ./dist 78 | twine upload dist/* 79 | - name: Get release version 80 | id: get-version 81 | run: | 82 | version=$(echo ${{github.event.release.tag_name}} | sed 's/v//') 83 | echo "get_release_ver=$version" >> "$GITHUB_ENV" 84 | - name: Print version 85 | run: | 86 | echo "${{ env.get_release_ver }}" 87 | - name: Attach whl artifact to release 88 | uses: actions/upload-release-asset@v1 89 | env: 90 | GITHUB_TOKEN: ${{ github.token }} 91 | with: 92 | upload_url: ${{ github.event.release.upload_url }} 93 | asset_path: ./dist/${{ format('coherence_client-{0}-py3-none-any.whl', env.get_release_ver) }} 94 | asset_name: ${{ format('coherence_client-{0}-py3-none-any.whl', env.get_release_ver ) }} 95 | asset_content_type: application/gzip 96 | - name: Attach tar.gz artifact to release 97 | uses: actions/upload-release-asset@v1 98 | env: 99 | GITHUB_TOKEN: ${{ github.token }} 100 | with: 101 | upload_url: ${{ github.event.release.upload_url }} 102 | asset_path: ./dist/${{ format('coherence_client-{0}.tar.gz', env.get_release_ver) }} 103 | asset_name: ${{ format('coherence_client-{0}.tar.gz', env.get_release_ver) }} 104 | asset_content_type: application/gzip 105 | - name: Attach doc zip to release 106 | uses: actions/upload-release-asset@v1 107 | env: 108 | GITHUB_TOKEN: ${{ github.token }} 109 | with: 110 | upload_url: ${{ github.event.release.upload_url }} 111 | asset_path: ./artifact.tar 112 | asset_name: ${{ format('coherence_client-{0}-doc.tar', env.get_release_ver) }} 113 | asset_content_type: application/gzip 114 | 115 | # Deploy job 116 | deploy: 117 | # Add a dependency to the build job 118 | needs: build 119 | 120 | # Deploy to the github-pages environment 121 | environment: 122 | name: github-pages 123 | url: ${{ steps.deployment.outputs.page_url }} 124 | 125 | # Specify runner + deployment step 126 | runs-on: ubuntu-latest 127 | steps: 128 | - name: Deploy to GitHub Pages 129 | id: deployment 130 | uses: actions/deploy-pages@v4 # or the latest "vX.X.X" version tag for this action 131 | with: 132 | artifact_name: github-pages 133 | -------------------------------------------------------------------------------- /.github/workflows/run-examples-with-release.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025, Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | name: Run Examples with released client 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "0 5 * * *" 10 | jobs: 11 | run-examples-with-latest-release: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | python-version: ["3.9.x", "3.10.x", "3.11.x", "3.12.x", "3.13.x"] 16 | os: [ubuntu-latest] 17 | coherence-image: 18 | - ghcr.io/oracle/coherence-ce 19 | coherenceVersion: 20 | - 22.06.12 21 | - 25.03.1 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - name: Get Docker Images 25 | shell: bash 26 | run: | 27 | docker pull ${{ matrix.coherence-image }}:${{ matrix.coherenceVersion }} 28 | 29 | - uses: actions/checkout@v4 30 | - uses: actions/setup-python@v5 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | 34 | - name: Install Coherence Python Client 35 | shell: bash 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install coherence-client 39 | pip install sentence-transformers # required for vector_search example 40 | 41 | - name: Start the Server using image 42 | shell: bash 43 | run: | 44 | docker run -d -p 1408:1408 ${{ matrix.coherence-image }}:${{ matrix.coherenceVersion }} 45 | sleep 20 46 | 47 | - name: Run the example 48 | shell: bash 49 | run: | 50 | cd examples 51 | for file in *.py; do 52 | echo "Run example ${file}" 53 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 python3 $file 54 | echo "==== Done running example ${file} ======" 55 | done 56 | -------------------------------------------------------------------------------- /.github/workflows/run-examples.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025, Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | name: Run Examples 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - '*' 11 | pull_request: 12 | branches: [ main ] 13 | jobs: 14 | run-examples: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: ["3.9.x"] 19 | poetry-version: ["1.8.4"] 20 | os: [ubuntu-latest] 21 | coherence-image: 22 | - ghcr.io/oracle/coherence-ce 23 | coherenceVersion: 24 | - 22.06.12 25 | - 25.03.1 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - name: Get Docker Images 29 | shell: bash 30 | run: | 31 | docker pull ${{ matrix.coherence-image }}:${{ matrix.coherenceVersion }} 32 | 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-python@v5 35 | with: 36 | python-version: ${{ matrix.python-version }} 37 | 38 | - name: Install Poetry 39 | shell: bash 40 | run: | 41 | pip install poetry==${{ matrix.poetry-version }} 42 | 43 | - name: Install Dependencies 44 | run: python -m poetry install 45 | 46 | - name: Install Coherence Python Client 47 | shell: bash 48 | run: | 49 | python -m pip install --upgrade pip 50 | python -m poetry run pip install sentence-transformers # required for vector_search example 51 | 52 | - name: Start the Server using image 53 | shell: bash 54 | run: | 55 | docker run -d -p 1408:1408 ${{ matrix.coherence-image }}:${{ matrix.coherenceVersion }} 56 | sleep 20 57 | 58 | - name: Run the example 59 | shell: bash 60 | run: | 61 | cd examples 62 | for file in *.py; do 63 | echo "Run example ${file}" 64 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 python -m poetry run python3 $file 65 | echo "==== Done running example ${file} ======" 66 | done 67 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | # --------------------------------------------------------------------------- 6 | # Coherence Python Client GitHub Actions Scheduled Trivy Scan 7 | # --------------------------------------------------------------------------- 8 | name: Scheduled Trivy Scan 9 | 10 | on: 11 | workflow_dispatch: 12 | schedule: 13 | # Every day at midnight 14 | - cron: '0 0 * * *' 15 | 16 | jobs: 17 | trivy-scan: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | 24 | - name: Run Trivy vulnerability scanner to scan repo 25 | uses: aquasecurity/trivy-action@0.31.0 26 | with: 27 | scan-type: 'fs' 28 | skip-dirs: 'tests/java' 29 | -------------------------------------------------------------------------------- /.github/workflows/validate-nslookup.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022, 2025, Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | name: CI nslookup Build 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "0 5 * * *" 10 | push: 11 | branches: 12 | - '*' 13 | pull_request: 14 | branches: [ main ] 15 | jobs: 16 | ci-nslookup: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | python-version: ["3.9.x"] 21 | poetry-version: ["1.8.4"] 22 | os: [ubuntu-latest] 23 | coherenceVersion: 24 | - 25.03.1 25 | - 22.06.12 26 | base-image: 27 | - gcr.io/distroless/java17-debian11 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - name: Set up JDK 31 | uses: actions/setup-java@v4 32 | with: 33 | java-version: '17' 34 | distribution: 'zulu' 35 | 36 | - uses: actions/checkout@v4 37 | - uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | 41 | - name: Cache Maven packages 42 | uses: actions/cache@v4 43 | with: 44 | path: ~/.m2 45 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 46 | restore-keys: ${{ runner.os }}-m2 47 | 48 | - name: Install Poetry 49 | shell: bash 50 | run: | 51 | pip install poetry==${{ matrix.poetry-version }} 52 | 53 | - name: Install Dependencies 54 | run: python -m poetry install 55 | 56 | - name: Configure Validation 57 | run: python -m poetry run make validate-setup 58 | 59 | - name: Validate Sources 60 | run: python -m poetry run make validate 61 | 62 | - name: Run Coherence Server 63 | shell: bash 64 | run: | 65 | export COHERENCE_VERSION=${{ matrix.coherenceVersion }} 66 | curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash 67 | cohctl version 68 | cohctl create cluster grpc-cluster -v ${{ matrix.coherenceVersion }} -y -a coherence-grpc-proxy 69 | sleep 20 70 | cohctl monitor health -n localhost:7574 -T 40 -w 71 | 72 | - name: Run test 73 | shell: bash 74 | run: | 75 | export COHERENCE_SERVER_ADDRESS=coherence:///localhost:7574 76 | python -m poetry run make test-nslookup 77 | 78 | - name: Archive server logs 79 | if: failure() 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: server-logs-${{matrix.python-version}}-${{ matrix.coherenceVersion }} 83 | path: ./tests/utils/*.txt 84 | retention-days: 10 85 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022, 2024, Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | name: CI Build 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "0 5 * * *" 10 | push: 11 | branches: 12 | - '*' 13 | pull_request: 14 | branches: [ main ] 15 | jobs: 16 | ci: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | python-version: ["3.9.x", "3.10.x", "3.11.x", "3.12.x", "3.13.x"] 21 | poetry-version: ["1.8.4"] 22 | os: [ubuntu-latest] 23 | coherenceVersion: 24 | - 25.03.1 25 | - 22.06.12 26 | base-image: 27 | - gcr.io/distroless/java17-debian11 28 | profile: 29 | - ",-jakarta,javax" 30 | - ",jakarta,-javax" 31 | exclude: 32 | - coherenceVersion: 25.03.1 33 | profile: ",-jakarta,javax" 34 | - coherenceVersion: 22.06.12 35 | profile: ",jakarta,-javax" 36 | 37 | runs-on: ${{ matrix.os }} 38 | steps: 39 | - name: Get Docker Images 40 | shell: bash 41 | run: | 42 | docker pull ${{ matrix.base-image }} 43 | uname -a 44 | 45 | - name: Set up JDK 46 | uses: actions/setup-java@v4 47 | with: 48 | java-version: '17' 49 | distribution: 'zulu' 50 | 51 | - uses: actions/checkout@v4 52 | - uses: actions/setup-python@v5 53 | with: 54 | python-version: ${{ matrix.python-version }} 55 | 56 | - name: Cache Maven packages 57 | uses: actions/cache@v4 58 | with: 59 | path: ~/.m2 60 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 61 | restore-keys: ${{ runner.os }}-m2 62 | 63 | - name: Install Poetry 64 | shell: bash 65 | run: | 66 | pip install poetry==${{ matrix.poetry-version }} 67 | 68 | - name: Install Dependencies 69 | run: python -m poetry install 70 | 71 | - name: Configure Validation 72 | run: python -m poetry run make validate-setup 73 | 74 | - name: Validate Sources 75 | run: python -m poetry run make validate 76 | 77 | - name: Run test 78 | shell: bash 79 | run: | 80 | python -m poetry run ./tests/scripts/run-tests.sh \ 81 | ${{ matrix.coherenceVersion }} \ 82 | ${{ matrix.base-image }} \ 83 | ${{ matrix.profile }} 84 | - name: Archive server logs 85 | if: failure() 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: server-logs-${{matrix.python-version}}-${{ matrix.coherenceVersion }} 89 | path: ./tests/utils/*.txt 90 | retention-days: 10 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # http://oss.oracle.com/licenses/upl. 4 | 5 | .idea 6 | .python-version 7 | .coverage 8 | .pytest_cache 9 | *.iml 10 | etc 11 | docs 12 | build 13 | poetry.lock 14 | __pycache__ 15 | tests/java/coherence-python-test/target 16 | tests/utils/certs 17 | tests/utils/.env 18 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | # See https://pre-commit.com for more information 6 | # See https://pre-commit.com/hooks.html for more hooks 7 | exclude: \w*(_pb2)\w* 8 | 9 | repos: 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v5.0.0 12 | hooks: 13 | - id: trailing-whitespace 14 | - id: end-of-file-fixer 15 | - id: check-yaml 16 | - id: check-added-large-files 17 | exclude: \.json.gzip 18 | 19 | - repo: https://github.com/PyCQA/flake8 20 | rev: 7.1.1 21 | hooks: 22 | - id: flake8 23 | 24 | - repo: https://github.com/psf/black 25 | rev: 24.10.0 26 | hooks: 27 | - id: black 28 | 29 | - repo: https://github.com/pre-commit/mirrors-mypy 30 | rev: v1.13.0 31 | hooks: 32 | - id: mypy 33 | 34 | - repo: https://github.com/PyCQA/isort 35 | rev: 5.13.2 36 | hooks: 37 | - id: isort 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | *Detailed instructions on how to contribute to the project, if applicable. Must include section about Oracle Contributor Agreement with link and instructions* 2 | 3 | # Contributing to this repository 4 | 5 | We welcome your contributions! There are multiple ways to contribute. 6 | 7 | ## Opening issues 8 | 9 | For bugs or enhancement requests, please file a GitHub issue unless it's 10 | security related. When filing a bug remember that the better written the bug is, 11 | the more likely it is to be fixed. If you think you've found a security 12 | vulnerability, do not raise a GitHub issue and follow the instructions in our 13 | [security policy](./SECURITY.md). 14 | 15 | ## Contributing code 16 | 17 | We welcome your code contributions. Before submitting code via a pull request, 18 | you will need to have signed the [Oracle Contributor Agreement][OCA] (OCA) and 19 | your commits need to include the following line using the name and e-mail 20 | address you used to sign the OCA: 21 | 22 | ```text 23 | Signed-off-by: Your Name 24 | ``` 25 | 26 | This can be automatically added to pull requests by committing with `--sign-off` 27 | or `-s`, e.g. 28 | 29 | ```text 30 | git commit --signoff 31 | ``` 32 | 33 | Only pull requests from committers that can be verified as having signed the OCA 34 | can be accepted. 35 | 36 | ## Pull request process 37 | 38 | 1. Ensure there is an issue created to track and discuss the fix or enhancement 39 | you intend to submit. 40 | 1. Fork this repository. 41 | 1. Create a branch in your fork to implement the changes. We recommend using 42 | the issue number as part of your branch name, e.g. `1234-fixes`. 43 | 1. Ensure that any documentation is updated with the changes that are required 44 | by your change. 45 | 1. Ensure that any samples are updated if the base image has been changed. 46 | 1. Submit the pull request. *Do not leave the pull request blank*. Explain exactly 47 | what your changes are meant to do and provide simple steps on how to validate. 48 | your changes. Ensure that you reference the issue you created as well. 49 | 1. We will assign the pull request to 2-3 people for review before it is merged. 50 | 51 | ## Code of conduct 52 | 53 | Follow the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule). If you'd 54 | like more specific guidelines, see the [Contributor Covenant Code of Conduct][COC]. 55 | 56 | [OCA]: https://oca.opensource.oracle.com 57 | [COC]: https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 58 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Developing Coherence Python Client 9 | 10 | ### Pre-Requisites 11 | * Use `bash` shell 12 | * [pyenv](https://github.com/pyenv/pyenv) version 2.3.x or later 13 | * [poetry](https://github.com/python-poetry/poetry) version 1.5.x or later 14 | 15 | ### Project Structure 16 | * `bin` - various shell scripts 17 | * `etc` - contains the ProtoBuff .protoc files and other various files 18 | * `coherence` - Python source files and related resources 19 | * `tests` - contains the library test cases in plain Python 20 | 21 | ### Setup working environment 22 | 1. Install Python version 3.11.3 23 | 24 | ```pyenv install 3.11.3``` 25 | 26 | 2. Checkout the source from GitHub and change directory to the project root dir 27 | 28 | 3. Set pyenv local version for this project to 3.11.3 29 | 30 | ```pyenv local 3.11.3``` 31 | 4. Set poetry to use python 3.11.3 32 | 33 | ```poetry env use 3.11.3``` 34 | 5. Install all the required dependencies using poetry 35 | 36 | ```poetry install``` 37 | 6. For setting up IntelliJ IDE, install the Python and PyEnv plugins 38 | 7. Get the full path of the poetry/python virtualenv 39 | 40 | ```poetry env list --full-path``` 41 | 8. Use the path above to setup the project settings in IntelliJ IDE. See [this](https://www.jetbrains.com/help/idea/creating-virtual-environment.html) 42 | 43 | Use the *Existing Environment* option to use the path above 44 | 45 | **Note:** Incase one needs to re-generate the sources from .proto files, make sure steps 1-5 are done. 46 | Run the Makefile in the virtualenv shell: 47 | ``` 48 | poetry shell 49 | make generate-proto 50 | ``` 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Oracle and/or its affiliates. 2 | 3 | The Universal Permissive License (UPL), Version 1.0 4 | 5 | Subject to the condition set forth below, permission is hereby granted to any 6 | person obtaining a copy of this software, associated documentation and/or data 7 | (collectively the "Software"), free of charge and under any and all copyright 8 | rights in the Software, and any and all patent rights owned or freely 9 | licensable by each licensor hereunder covering either (i) the unmodified 10 | Software as contributed to or provided by such licensor, or (ii) the Larger 11 | Works (as defined below), to deal in both 12 | 13 | (a) the Software, and 14 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if 15 | one is included with the Software (each a "Larger Work" to which the Software 16 | is contributed by such licensors), 17 | 18 | without restriction, including without limitation the rights to copy, create 19 | derivative works of, display, perform, and distribute the Software and make, 20 | use, sell, offer for sale, import, export, have made, and have sold the 21 | Software and the Larger Work(s), and to sublicense the foregoing rights on 22 | either these or other terms. 23 | 24 | This license is subject to the following condition: 25 | The above copyright notice and either this complete permission notice or at 26 | a minimum a reference to the UPL must be included in all copies or 27 | substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Client for Oracle Coherence 2 | 3 | ![CI/CD](https://github.com/oracle/coherence-py-client/actions/workflows/validate.yml/badge.svg) 4 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=oracle_coherence-py-client&metric=alert_status)](https://sonarcloud.io/project/overview?id=oracle_coherence-py-client) 5 | [![License](http://img.shields.io/badge/license-UPL%201.0-blue.svg)](https://oss.oracle.com/licenses/upl/) 6 | 7 | 8 | 9 | The Coherence Python Client allows Python applications to act as cache clients to an Oracle Coherence cluster using gRPC as the network transport. 10 | 11 | #### Features 12 | * Familiar Map-like interface for manipulating cache entries including but not limited to: 13 | * `put`, `put_if_absent`, `put_all`, `get`, `get_all`, `remove`, `clear`, `get_or_default`, `replace`, `replace_mapping`, `size`, `is_empty`, `contains_key`, `contains_value` 14 | * Cluster-side querying, aggregation and filtering of map entries 15 | * Cluster-side manipulation of map entries using EntryProcessors 16 | * Registration of listeners to be notified of: 17 | * mutations such as insert, update and delete on Maps 18 | * map lifecycle events such as truncated, released or destroyed 19 | * session lifecycle events such as connected, disconnected, reconnected and closed 20 | * Support for storing Python objects as JSON as well as the ability to serialize to Java objects on the server for access from other Coherence language API's 21 | 22 | #### Requirements 23 | * [Coherence CE](https://github.com/oracle/coherence) 22.06.11+ or Coherence 14.1.1.2206.11+, 14.1.2.0+ Commercial edition with a configured [gRPCProxy](https://docs.oracle.com/en/middleware/standalone/coherence/14.1.1.2206/develop-remote-clients/using-coherence-grpc-server.html). 24 | * Usage of module `coherence.ai` requires [Coherence CE](https://github.com/oracle/coherence) 24.09.2+ 25 | * Python 3.9.x or later 26 | 27 | 28 | #### Starting a Coherence Cluster 29 | 30 | Before testing the Python client, you must ensure a Coherence cluster is available. 31 | For local development, we recommend using the Coherence CE Docker image; it contains 32 | everything necessary for the client to operate correctly. 33 | 34 | ```bash 35 | docker run -d -p 1408:1408 ghcr.io/oracle/coherence-ce:25.03.1 36 | ``` 37 | 38 | ## Installation 39 | 40 | ```bash 41 | python3 -m pip install coherence-client 42 | ``` 43 | 44 | ## Documentation 45 | 46 | [https://oracle.github.io/coherence-py-client/](https://oracle.github.io/coherence-py-client/) 47 | 48 | ## Examples 49 | 50 | The following example connects to a Coherence cluster running gRPC Proxy on default 51 | port of 1408, creates a new `NamedCache` with key `str` and value of a `str|int` and 52 | issues `put()`, `get()`, `size()` and `remove` operations. 53 | 54 | ```python 55 | from coherence import NamedCache, Session 56 | import asyncio 57 | from typing import Union 58 | 59 | 60 | async def run_test(): 61 | 62 | # create a new Session to the Coherence server 63 | session: Session = await Session.create() 64 | 65 | # create a new NamedCache with key of string|int and value of string|int 66 | cache: NamedCache[str, Union[str,int]] = await session.get_cache("test") 67 | 68 | # put a new key/value 69 | k: str = "one" 70 | v: str = "only-one" 71 | await cache.put(k, v) 72 | 73 | # get the value for a key in the cache 74 | r = await cache.get(k) 75 | 76 | # print the value got for a key in the cache 77 | print("The value of key \"one\" is " + r) 78 | 79 | k1: str = "two" 80 | v1: int = 2 81 | await cache.put(k1, v1) 82 | r = await cache.get(k1) 83 | print("The value of key \"two\" is " + str(r)) 84 | 85 | # print the size of the cache 86 | print("Size of the cache test is " + str(await cache.size())) 87 | 88 | # remove an entry from the cache 89 | await cache.remove(k1) 90 | 91 | 92 | # run the test 93 | asyncio.run(run_test()) 94 | ``` 95 | ## Help 96 | 97 | We have a **public Slack channel** where you can get in touch with us to ask questions about using the Coherence Python Client 98 | or give us feedback or suggestions about what features and improvements you would like to see. We would love 99 | to hear from you. To join our channel, 100 | please [visit this site to get an invitation](https://join.slack.com/t/oraclecoherence/shared_invite/enQtNzcxNTQwMTAzNjE4LTJkZWI5ZDkzNGEzOTllZDgwZDU3NGM2YjY5YWYwMzM3ODdkNTU2NmNmNDFhOWIxMDZlNjg2MzE3NmMxZWMxMWE). 101 | The invitation email will include details of how to access our Slack 102 | workspace. After you are logged in, please come to `#coherence` and say, "hello!" 103 | 104 | If you would like to raise an issue please see [here](https://github.com/oracle/coherence-py-client/issues/new/choose). 105 | 106 | ## Contributing 107 | 108 | This project welcomes contributions from the community. Before submitting a pull request, please [review our contribution guide](./CONTRIBUTING.md) 109 | 110 | ## Security 111 | 112 | Please consult the [security guide](./SECURITY.md) for our responsible security vulnerability disclosure process 113 | 114 | ## License 115 | 116 | Copyright (c) 2023, 2025, Oracle and/or its affiliates. 117 | 118 | Released under the Universal Permissive License v1.0 as shown at 119 | . 120 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security vulnerabilities 2 | 3 | Oracle values the independent security research community and believes that 4 | responsible disclosure of security vulnerabilities helps us ensure the security 5 | and privacy of all our users. 6 | 7 | Please do NOT raise a GitHub Issue to report a security vulnerability. If you 8 | believe you have found a security vulnerability, please submit a report to 9 | [secalert_us@oracle.com][1] preferably with a proof of concept. Please review 10 | some additional information on [how to report security vulnerabilities to Oracle][2]. 11 | We encourage people who contact Oracle Security to use email encryption using 12 | [our encryption key][3]. 13 | 14 | We ask that you do not use other channels or contact the project maintainers 15 | directly. 16 | 17 | Non-vulnerability related security issues including ideas for new or improved 18 | security features are welcome on GitHub Issues. 19 | 20 | ## Security updates, alerts and bulletins 21 | 22 | Security updates will be released on a regular cadence. Many of our projects 23 | will typically release security fixes in conjunction with the 24 | Oracle Critical Patch Update program. Additional 25 | information, including past advisories, is available on our [security alerts][4] 26 | page. 27 | 28 | ## Security-related information 29 | 30 | We will provide security related information such as a threat model, considerations 31 | for secure use, or any known security issues in our documentation. Please note 32 | that labs and sample code are intended to demonstrate a concept and may not be 33 | sufficiently hardened for production use. 34 | 35 | [1]: mailto:secalert_us@oracle.com 36 | [2]: https://www.oracle.com/corporate/security-practices/assurance/vulnerability/reporting.html 37 | [3]: https://www.oracle.com/security-alerts/encryptionkey.html 38 | [4]: https://www.oracle.com/security-alerts/ 39 | -------------------------------------------------------------------------------- /bin/docker-utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright (c) 2000, 2022, 2023, Oracle and/or its affiliates. 4 | # 5 | # Licensed under the Universal Permissive License v 1.0 as shown at 6 | # http://oss.oracle.com/licenses/upl. 7 | 8 | set -e 9 | 10 | declare -r ROOT="${PWD}" 11 | declare -r CONTAINER_NAME="coherence-py-test-container" 12 | declare -r IMAGE_NAME="ghcr.io/oracle/coherence-ce:22.06.11" 13 | 14 | function coh_up() { 15 | declare -r CONTAINER_ID=$(docker ps -a -q -f name="${CONTAINER_NAME}") 16 | if [[ -n "${CONTAINER_ID}" ]]; then 17 | docker start "${CONTAINER_ID}" 18 | else 19 | docker run -d -p 1408:1408 -p 5005:5005 -p 9999:9999 -p 30000:30000 --name "${CONTAINER_NAME}" -v \ 20 | "${ROOT}"/etc:/args "${IMAGE_NAME}" 21 | fi 22 | } 23 | 24 | function coh_down() { 25 | declare -r CONTAINER_ID=$(docker ps -q -f name="${CONTAINER_NAME}") 26 | if [[ -n "${CONTAINER_ID}" ]]; then 27 | docker stop "${CONTAINER_ID}" 28 | fi 29 | } 30 | 31 | function coh_clean() { 32 | coh_down 33 | declare -r CONTAINER_ID=$(docker ps -a -q -f name="${CONTAINER_NAME}") 34 | if [[ -n "${CONTAINER_ID}" ]]; then 35 | docker rm "${CONTAINER_ID}" 36 | fi 37 | } 38 | 39 | while getopts "udc" OPTION; do 40 | case "${OPTION}" in 41 | u) 42 | coh_up 43 | ;; 44 | d) 45 | coh_down 46 | ;; 47 | c) 48 | coh_clean 49 | ;; 50 | ?) 51 | echo "Usage: $(basename "$0") [-u] [-d]" 52 | ;; 53 | esac 54 | done 55 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) 2022, Oracle and/or its affiliates. 3 | # Licensed under the Universal Permissive License v 1.0 as shown at 4 | # https://oss.oracle.com/licenses/upl. 5 | # 6 | # ---------------------------------------------------------------------------------------------------------------------- 7 | # Minimal makefile for Sphinx documentation 8 | # 9 | 10 | # You can set these variables from the command line, and also 11 | # from the environment for the first two. 12 | SPHINXOPTS ?= 13 | SPHINXBUILD ?= sphinx-build 14 | SOURCEDIR = ../src/coherence 15 | BUILDDIR = _build 16 | 17 | # Put it first so that "make" without argument is like "make help". 18 | help: 19 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | 21 | .PHONY: help Makefile 22 | 23 | # Catch-all target: route all unknown targets to Sphinx using the new 24 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 25 | %: Makefile 26 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 27 | -------------------------------------------------------------------------------- /docs/aggregation.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Aggregation 7 | =========== 8 | 9 | Sometimes you don't need the actual data objects that are stored within the 10 | data grid, but the derived, calculated result based on them. This is where 11 | Coherence aggregation features come in handy. 12 | 13 | Aggregations can be executed against the whole data set, or they can be 14 | limited to a subset of the data using a query or a key set. See the utility 15 | class `Aggregators `_ for the aggregators 16 | supported out-of-the-box by this client. 17 | 18 | The following example demonstrates various aggregation operations against 19 | a `NamedMap`: 20 | 21 | .. literalinclude:: ../examples/aggregators.py 22 | :language: python 23 | :emphasize-lines: 47, 50, 53, 56, 60 24 | :linenos: 25 | 26 | * Line 47 - Returns a list of distinct hobbies across all entries 27 | * Line 50 - Returns a count of all Hobbits 28 | * Line 53 - Returns a count of all Hobbits over age 40 29 | * Line 56 - Returns an average of all Hobbits under the age of 40 30 | * Line 60 - For each hobby, list the oldest Hobbit for that interest 31 | -------------------------------------------------------------------------------- /docs/api_reference.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2025, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Python Client API Reference for Oracle Coherence 7 | ================================================ 8 | 9 | Aggregators 10 | ----------- 11 | .. autoclass:: coherence.Aggregators 12 | :members: 13 | 14 | CacheOptions 15 | ------------ 16 | .. autoclass:: coherence.CacheOptions 17 | :members: 18 | 19 | .. automethod:: __init__ 20 | 21 | CacheStats 22 | ------------ 23 | .. autoclass:: coherence.CacheStats 24 | :members: 25 | 26 | Comparator 27 | ---------- 28 | .. autoclass:: coherence.Comparator 29 | :members: 30 | 31 | Extractors 32 | ---------- 33 | .. autoclass:: coherence.Extractors 34 | :members: 35 | 36 | Filters 37 | ------- 38 | .. autoclass:: coherence.Filters 39 | :members: 40 | 41 | MapEntry 42 | -------- 43 | .. autoclass:: coherence.MapEntry 44 | :show-inheritance: 45 | :members: 46 | 47 | MapEvent 48 | ----------- 49 | .. autoclass:: coherence.event.MapEvent 50 | :show-inheritance: 51 | :members: 52 | 53 | MapListener 54 | ----------- 55 | .. autoclass:: coherence.event.MapListener 56 | :show-inheritance: 57 | :members: 58 | 59 | .. automethod:: __init__ 60 | 61 | NamedCache 62 | ---------- 63 | .. autoclass:: coherence.NamedCache 64 | :show-inheritance: 65 | :members: 66 | 67 | NamedMap 68 | -------- 69 | .. autoclass:: coherence.NamedMap 70 | :show-inheritance: 71 | :members: 72 | 73 | NearCacheOptions 74 | ---------------- 75 | .. autoclass:: coherence.NearCacheOptions 76 | :members: 77 | 78 | .. automethod:: __init__ 79 | 80 | Options 81 | ------- 82 | .. autoclass:: coherence.Options 83 | :show-inheritance: 84 | :members: 85 | 86 | .. automethod:: __init__ 87 | 88 | Processors 89 | ---------- 90 | .. autoclass:: coherence.Processors 91 | :members: 92 | 93 | Session 94 | ------- 95 | .. autoclass:: coherence.Session 96 | :show-inheritance: 97 | :members: 98 | 99 | .. automethod:: __init__ 100 | 101 | TlsOptions 102 | ---------- 103 | .. autoclass:: coherence.TlsOptions 104 | :show-inheritance: 105 | :members: 106 | 107 | .. automethod:: __init__ 108 | 109 | Modules 110 | ------- 111 | .. toctree:: 112 | :maxdepth: 3 113 | :titlesonly: 114 | 115 | api_reference/ai.rst 116 | api_reference/aggregator.rst 117 | api_reference/comparator.rst 118 | api_reference/event.rst 119 | api_reference/extractor.rst 120 | api_reference/filter.rst 121 | api_reference/processor.rst 122 | -------------------------------------------------------------------------------- /docs/api_reference/aggregator.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | ==================== 7 | coherence.aggregator 8 | ==================== 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | EntryAggregator 13 | --------------- 14 | .. autoclass:: coherence.aggregator.EntryAggregator 15 | :show-inheritance: 16 | :members: 17 | 18 | .. automethod:: __init__ 19 | 20 | AbstractComparableAggregator 21 | ---------------------------- 22 | .. autoclass:: coherence.aggregator.AbstractComparableAggregator 23 | :show-inheritance: 24 | :members: 25 | 26 | .. automethod:: __init__ 27 | 28 | AbstractDoubleAggregator 29 | ------------------------ 30 | .. autoclass:: coherence.aggregator.AbstractDoubleAggregator 31 | :show-inheritance: 32 | :members: 33 | 34 | .. automethod:: __init__ 35 | 36 | CompositeAggregator 37 | ------------------- 38 | .. autoclass:: coherence.aggregator.CompositeAggregator 39 | :show-inheritance: 40 | :members: 41 | 42 | .. automethod:: __init__ 43 | 44 | MaxAggregator 45 | ------------- 46 | .. autoclass:: coherence.aggregator.MaxAggregator 47 | :show-inheritance: 48 | :members: 49 | 50 | .. automethod:: __init__ 51 | 52 | MinAggregator 53 | ------------- 54 | .. autoclass:: coherence.aggregator.MinAggregator 55 | :show-inheritance: 56 | :members: 57 | 58 | .. automethod:: __init__ 59 | 60 | SumAggregator 61 | ------------- 62 | .. autoclass:: coherence.aggregator.SumAggregator 63 | :show-inheritance: 64 | :members: 65 | 66 | .. automethod:: __init__ 67 | 68 | AverageAggregator 69 | ----------------- 70 | .. autoclass:: coherence.aggregator.AverageAggregator 71 | :show-inheritance: 72 | :members: 73 | 74 | .. automethod:: __init__ 75 | 76 | CountAggregator 77 | --------------- 78 | .. autoclass:: coherence.aggregator.CountAggregator 79 | :show-inheritance: 80 | :members: 81 | 82 | .. automethod:: __init__ 83 | 84 | DistinctValuesAggregator 85 | ------------------------ 86 | .. autoclass:: coherence.aggregator.DistinctValuesAggregator 87 | :show-inheritance: 88 | :members: 89 | 90 | .. automethod:: __init__ 91 | 92 | TopAggregator 93 | ------------- 94 | .. autoclass:: coherence.aggregator.TopAggregator 95 | :show-inheritance: 96 | :members: 97 | 98 | .. automethod:: __init__ 99 | 100 | GroupAggregator 101 | --------------- 102 | .. autoclass:: coherence.aggregator.GroupAggregator 103 | :show-inheritance: 104 | :members: 105 | 106 | .. automethod:: __init__ 107 | 108 | Timeout 109 | ------- 110 | .. autoclass:: coherence.aggregator.Timeout 111 | :show-inheritance: 112 | :members: 113 | 114 | Schedule 115 | -------- 116 | .. autoclass:: coherence.aggregator.Schedule 117 | :show-inheritance: 118 | :members: 119 | 120 | PriorityAggregator 121 | ------------------ 122 | .. autoclass:: coherence.aggregator.PriorityAggregator 123 | :show-inheritance: 124 | :members: 125 | 126 | .. automethod:: __init__ 127 | 128 | QueryRecorder 129 | ------------- 130 | .. autoclass:: coherence.aggregator.QueryRecorder 131 | :show-inheritance: 132 | :members: 133 | 134 | .. automethod:: __init__ 135 | 136 | ReducerAggregator 137 | ----------------- 138 | .. autoclass:: coherence.aggregator.ReducerAggregator 139 | :show-inheritance: 140 | :members: 141 | 142 | .. automethod:: __init__ 143 | 144 | Aggregators 145 | ----------- 146 | .. autoclass:: coherence.aggregator.Aggregators 147 | :members: 148 | -------------------------------------------------------------------------------- /docs/api_reference/ai.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2025, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | ======================================= 7 | coherence.ai [24.09.2+ server required] 8 | ======================================= 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | 13 | Vector 14 | ------ 15 | .. autoclass:: coherence.ai.Vector 16 | :show-inheritance: 17 | :members: 18 | 19 | .. automethod:: __init__ 20 | 21 | BitVector 22 | --------- 23 | .. autoclass:: coherence.ai.BitVector 24 | :show-inheritance: 25 | :members: 26 | 27 | .. automethod:: __init__ 28 | 29 | ByteVector 30 | ---------- 31 | .. autoclass:: coherence.ai.ByteVector 32 | :show-inheritance: 33 | :members: 34 | 35 | .. automethod:: __init__ 36 | 37 | FloatVector 38 | ----------- 39 | .. autoclass:: coherence.ai.FloatVector 40 | :show-inheritance: 41 | :members: 42 | 43 | .. automethod:: __init__ 44 | 45 | DocumentChunk 46 | ------------- 47 | .. autoclass:: coherence.ai.DocumentChunk 48 | :show-inheritance: 49 | :members: 50 | 51 | .. automethod:: __init__ 52 | 53 | DistanceAlgorithm 54 | ----------------- 55 | .. autoclass:: coherence.ai.DistanceAlgorithm 56 | :show-inheritance: 57 | :members: 58 | 59 | .. automethod:: __init__ 60 | 61 | CosineDistance 62 | -------------- 63 | .. autoclass:: coherence.ai.CosineDistance 64 | :show-inheritance: 65 | :members: 66 | 67 | .. automethod:: __init__ 68 | 69 | InnerProductDistance 70 | -------------------- 71 | .. autoclass:: coherence.ai.InnerProductDistance 72 | :show-inheritance: 73 | :members: 74 | 75 | .. automethod:: __init__ 76 | 77 | L2SquaredDistance 78 | ----------------- 79 | .. autoclass:: coherence.ai.L2SquaredDistance 80 | :show-inheritance: 81 | :members: 82 | 83 | .. automethod:: __init__ 84 | 85 | SimilaritySearch 86 | ----------------- 87 | .. autoclass:: coherence.ai.SimilaritySearch 88 | :show-inheritance: 89 | :members: 90 | 91 | .. automethod:: __init__ 92 | 93 | QueryResult 94 | ----------- 95 | .. autoclass:: coherence.ai.QueryResult 96 | :show-inheritance: 97 | :members: 98 | 99 | .. automethod:: __init__ 100 | 101 | BinaryQuantIndex 102 | ---------------- 103 | .. autoclass:: coherence.ai.BinaryQuantIndex 104 | :show-inheritance: 105 | :members: 106 | 107 | .. automethod:: __init__ 108 | -------------------------------------------------------------------------------- /docs/api_reference/comparator.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2024, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | ==================== 7 | coherence.comparator 8 | ==================== 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | ExtractorComparator 13 | ------------------- 14 | .. autoclass:: coherence.comparator.ExtractorComparator 15 | :show-inheritance: 16 | 17 | InverseComparator 18 | ----------------- 19 | .. autoclass:: coherence.comparator.InverseComparator 20 | :show-inheritance: 21 | 22 | SafeComparator 23 | -------------- 24 | .. autoclass:: coherence.comparator.SafeComparator 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /docs/api_reference/event.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | ==================== 7 | coherence.event 8 | ==================== 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | MapEvent 13 | -------- 14 | .. autoclass:: coherence.event.MapEvent 15 | :show-inheritance: 16 | :members: 17 | :noindex: 18 | 19 | MapLifecycleEvent 20 | ----------------- 21 | .. autoclass:: coherence.event.MapLifecycleEvent 22 | :show-inheritance: 23 | :members: 24 | :noindex: 25 | 26 | MapEventType 27 | ------------ 28 | .. autoclass:: coherence.event.MapEventType 29 | :show-inheritance: 30 | :members: 31 | :noindex: 32 | 33 | MapListener 34 | ----------- 35 | .. autoclass:: coherence.event.MapListener 36 | :show-inheritance: 37 | :members: 38 | :noindex: 39 | -------------------------------------------------------------------------------- /docs/api_reference/extractor.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | =================== 7 | coherence.extractor 8 | =================== 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | Extractors 13 | ---------- 14 | .. autoclass:: coherence.extractor.Extractors 15 | :members: 16 | 17 | ValueExtractor 18 | --------------- 19 | .. autoclass:: coherence.extractor.ValueExtractor 20 | :show-inheritance: 21 | :members: 22 | 23 | .. automethod:: __init__ 24 | 25 | UniversalExtractor 26 | ------------------ 27 | .. autoclass:: coherence.extractor.UniversalExtractor 28 | :show-inheritance: 29 | :members: 30 | 31 | .. automethod:: __init__ 32 | 33 | AbstractCompositeExtractor 34 | -------------------------- 35 | .. autoclass:: coherence.extractor.AbstractCompositeExtractor 36 | :show-inheritance: 37 | :members: 38 | 39 | .. automethod:: __init__ 40 | 41 | ChainedExtractor 42 | ---------------- 43 | .. autoclass:: coherence.extractor.ChainedExtractor 44 | :show-inheritance: 45 | :members: 46 | 47 | .. automethod:: __init__ 48 | 49 | IdentityExtractor 50 | ----------------- 51 | .. autoclass:: coherence.extractor.IdentityExtractor 52 | :show-inheritance: 53 | :members: 54 | 55 | .. automethod:: __init__ 56 | 57 | ValueUpdater 58 | ------------ 59 | .. autoclass:: coherence.extractor.ValueUpdater 60 | :show-inheritance: 61 | :members: 62 | 63 | .. automethod:: __init__ 64 | 65 | ValueManipulator 66 | ---------------- 67 | .. autoclass:: coherence.extractor.ValueManipulator 68 | :show-inheritance: 69 | :members: 70 | 71 | .. automethod:: __init__ 72 | 73 | CompositeUpdater 74 | ---------------- 75 | .. autoclass:: coherence.extractor.CompositeUpdater 76 | :show-inheritance: 77 | :members: 78 | 79 | .. automethod:: __init__ 80 | 81 | UniversalUpdater 82 | ---------------- 83 | .. autoclass:: coherence.extractor.UniversalUpdater 84 | :show-inheritance: 85 | :members: 86 | 87 | .. automethod:: __init__ 88 | -------------------------------------------------------------------------------- /docs/api_reference/filter.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | ================ 7 | coherence.filter 8 | ================ 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | Filter 13 | ------ 14 | .. autoclass:: coherence.filter.Filter 15 | :show-inheritance: 16 | :members: 17 | 18 | Filters 19 | ------- 20 | .. autoclass:: coherence.filter.Filters 21 | :members: 22 | 23 | ExtractorFilter 24 | --------------- 25 | .. autoclass:: coherence.filter.ExtractorFilter 26 | :show-inheritance: 27 | :members: 28 | 29 | .. automethod:: __init__ 30 | 31 | ComparisonFilter 32 | ---------------- 33 | .. autoclass:: coherence.filter.ComparisonFilter 34 | :show-inheritance: 35 | :members: 36 | 37 | .. automethod:: __init__ 38 | 39 | EqualsFilter 40 | ------------ 41 | .. autoclass:: coherence.filter.EqualsFilter 42 | :show-inheritance: 43 | :members: 44 | 45 | .. automethod:: __init__ 46 | 47 | NotEqualsFilter 48 | --------------- 49 | .. autoclass:: coherence.filter.NotEqualsFilter 50 | :show-inheritance: 51 | :members: 52 | 53 | .. automethod:: __init__ 54 | 55 | GreaterFilter 56 | ------------- 57 | .. autoclass:: coherence.filter.GreaterFilter 58 | :show-inheritance: 59 | :members: 60 | 61 | .. automethod:: __init__ 62 | 63 | GreaterEqualsFilter 64 | ------------------- 65 | .. autoclass:: coherence.filter.GreaterEqualsFilter 66 | :show-inheritance: 67 | :members: 68 | 69 | .. automethod:: __init__ 70 | 71 | LessFilter 72 | ---------- 73 | .. autoclass:: coherence.filter.LessFilter 74 | :show-inheritance: 75 | :members: 76 | 77 | .. automethod:: __init__ 78 | 79 | NotFilter 80 | --------- 81 | .. autoclass:: coherence.filter.NotFilter 82 | :show-inheritance: 83 | :members: 84 | 85 | .. automethod:: __init__ 86 | 87 | IsNoneFilter 88 | ------------ 89 | .. autoclass:: coherence.filter.IsNoneFilter 90 | :show-inheritance: 91 | :members: 92 | 93 | .. automethod:: __init__ 94 | 95 | IsNotNoneFilter 96 | ---------------- 97 | .. autoclass:: coherence.filter.IsNotNoneFilter 98 | :show-inheritance: 99 | :members: 100 | 101 | .. automethod:: __init__ 102 | 103 | AlwaysFilter 104 | ------------ 105 | .. autoclass:: coherence.filter.AlwaysFilter 106 | :show-inheritance: 107 | :members: 108 | 109 | .. automethod:: __init__ 110 | 111 | NeverFilter 112 | ----------- 113 | .. autoclass:: coherence.filter.NeverFilter 114 | :show-inheritance: 115 | :members: 116 | 117 | .. automethod:: __init__ 118 | 119 | ArrayFilter 120 | ----------- 121 | .. autoclass:: coherence.filter.ArrayFilter 122 | :show-inheritance: 123 | :members: 124 | 125 | .. automethod:: __init__ 126 | 127 | AllFilter 128 | --------- 129 | .. autoclass:: coherence.filter.AllFilter 130 | :show-inheritance: 131 | :members: 132 | 133 | .. automethod:: __init__ 134 | 135 | AnyFilter 136 | --------- 137 | .. autoclass:: coherence.filter.AnyFilter 138 | :show-inheritance: 139 | :members: 140 | 141 | .. automethod:: __init__ 142 | 143 | AndFilter 144 | --------- 145 | .. autoclass:: coherence.filter.AndFilter 146 | :show-inheritance: 147 | :members: 148 | 149 | .. automethod:: __init__ 150 | 151 | OrFilter 152 | -------- 153 | .. autoclass:: coherence.filter.OrFilter 154 | :show-inheritance: 155 | :members: 156 | 157 | .. automethod:: __init__ 158 | 159 | XorFilter 160 | --------- 161 | .. autoclass:: coherence.filter.XorFilter 162 | :show-inheritance: 163 | :members: 164 | 165 | .. automethod:: __init__ 166 | 167 | BetweenFilter 168 | ------------- 169 | .. autoclass:: coherence.filter.BetweenFilter 170 | :show-inheritance: 171 | :members: 172 | 173 | .. automethod:: __init__ 174 | 175 | ContainsFilter 176 | -------------- 177 | .. autoclass:: coherence.filter.ContainsFilter 178 | :show-inheritance: 179 | :members: 180 | 181 | .. automethod:: __init__ 182 | 183 | ContainsAnyFilter 184 | ----------------- 185 | .. autoclass:: coherence.filter.ContainsAnyFilter 186 | :show-inheritance: 187 | :members: 188 | 189 | .. automethod:: __init__ 190 | 191 | ContainsAllFilter 192 | ----------------- 193 | .. autoclass:: coherence.filter.ContainsAllFilter 194 | :show-inheritance: 195 | :members: 196 | 197 | .. automethod:: __init__ 198 | 199 | InFilter 200 | -------- 201 | .. autoclass:: coherence.filter.InFilter 202 | :show-inheritance: 203 | :members: 204 | 205 | .. automethod:: __init__ 206 | 207 | LikeFilter 208 | ---------- 209 | .. autoclass:: coherence.filter.LikeFilter 210 | :show-inheritance: 211 | :members: 212 | 213 | .. automethod:: __init__ 214 | 215 | RegexFilter 216 | ----------- 217 | .. autoclass:: coherence.filter.RegexFilter 218 | :show-inheritance: 219 | :members: 220 | 221 | .. automethod:: __init__ 222 | 223 | PredicateFilter 224 | --------------- 225 | .. autoclass:: coherence.filter.PredicateFilter 226 | :show-inheritance: 227 | :members: 228 | 229 | .. automethod:: __init__ 230 | 231 | PresentFilter 232 | ------------- 233 | .. autoclass:: coherence.filter.PresentFilter 234 | :show-inheritance: 235 | :members: 236 | 237 | .. automethod:: __init__ 238 | 239 | MapEventFilter 240 | -------------- 241 | .. autoclass:: coherence.filter.MapEventFilter 242 | :show-inheritance: 243 | :members: 244 | 245 | .. automethod:: __init__ 246 | -------------------------------------------------------------------------------- /docs/api_reference/processor.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | =================== 7 | coherence.processor 8 | =================== 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | Processors 13 | --------------- 14 | .. autoclass:: coherence.processor.Processors 15 | :members: 16 | 17 | EntryProcessor 18 | -------------- 19 | .. autoclass:: coherence.processor.EntryProcessor 20 | :show-inheritance: 21 | :members: 22 | 23 | .. automethod:: __init__ 24 | 25 | ExtractorProcessor 26 | ------------------ 27 | .. autoclass:: coherence.processor.ExtractorProcessor 28 | :show-inheritance: 29 | :members: 30 | 31 | .. automethod:: __init__ 32 | 33 | CompositeProcessor 34 | ------------------ 35 | .. autoclass:: coherence.processor.CompositeProcessor 36 | :show-inheritance: 37 | :members: 38 | :inherited-members: 39 | 40 | .. automethod:: __init__ 41 | 42 | ConditionalProcessor 43 | -------------------- 44 | .. autoclass:: coherence.processor.ConditionalProcessor 45 | :show-inheritance: 46 | :members: 47 | 48 | .. automethod:: __init__ 49 | 50 | NullProcessor 51 | ------------- 52 | .. autoclass:: coherence.processor.NullProcessor 53 | :show-inheritance: 54 | :members: 55 | 56 | .. automethod:: __init__ 57 | 58 | PropertyProcessor 59 | ----------------- 60 | .. autoclass:: coherence.processor.PropertyProcessor 61 | :show-inheritance: 62 | :members: 63 | 64 | .. automethod:: __init__ 65 | 66 | PropertyManipulator 67 | ------------------- 68 | .. autoclass:: coherence.processor.PropertyManipulator 69 | :show-inheritance: 70 | :members: 71 | 72 | .. automethod:: __init__ 73 | 74 | NumberMultiplier 75 | ---------------- 76 | .. autoclass:: coherence.processor.NumberMultiplier 77 | :show-inheritance: 78 | :members: 79 | 80 | .. automethod:: __init__ 81 | 82 | NumberIncrementor 83 | ----------------- 84 | .. autoclass:: coherence.processor.NumberIncrementor 85 | :show-inheritance: 86 | :members: 87 | 88 | .. automethod:: __init__ 89 | 90 | ConditionalPut 91 | -------------- 92 | .. autoclass:: coherence.processor.ConditionalPut 93 | :show-inheritance: 94 | :members: 95 | 96 | .. automethod:: __init__ 97 | 98 | ConditionalPutAll 99 | ----------------- 100 | .. autoclass:: coherence.processor.ConditionalPutAll 101 | :show-inheritance: 102 | :members: 103 | 104 | .. automethod:: __init__ 105 | 106 | ConditionalRemove 107 | ----------------- 108 | .. autoclass:: coherence.processor.ConditionalRemove 109 | :show-inheritance: 110 | :members: 111 | 112 | .. automethod:: __init__ 113 | 114 | MethodInvocationProcessor 115 | ------------------------- 116 | .. autoclass:: coherence.processor.MethodInvocationProcessor 117 | :show-inheritance: 118 | :members: 119 | 120 | .. automethod:: __init__ 121 | 122 | TouchProcessor 123 | -------------- 124 | .. autoclass:: coherence.processor.TouchProcessor 125 | :show-inheritance: 126 | :members: 127 | 128 | .. automethod:: __init__ 129 | 130 | ScriptProcessor 131 | --------------- 132 | .. autoclass:: coherence.processor.ScriptProcessor 133 | :show-inheritance: 134 | :members: 135 | 136 | .. automethod:: __init__ 137 | 138 | PreloadRequest 139 | -------------- 140 | .. autoclass:: coherence.processor.PreloadRequest 141 | :show-inheritance: 142 | :members: 143 | 144 | .. automethod:: __init__ 145 | 146 | UpdaterProcessor 147 | ---------------- 148 | .. autoclass:: coherence.processor.UpdaterProcessor 149 | :show-inheritance: 150 | :members: 151 | 152 | .. automethod:: __init__ 153 | 154 | VersionedPut 155 | ------------ 156 | .. autoclass:: coherence.processor.VersionedPut 157 | :show-inheritance: 158 | :members: 159 | 160 | .. automethod:: __init__ 161 | 162 | VersionedPutAll 163 | --------------- 164 | .. autoclass:: coherence.processor.VersionedPutAll 165 | :show-inheritance: 166 | :members: 167 | 168 | .. automethod:: __init__ 169 | -------------------------------------------------------------------------------- /docs/basics.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | The Basics 7 | ========== 8 | 9 | `NamedMap `_ and `NamedCache `_ are `dict`-like structures allowing users 10 | to store data within a remote `Coherence `_ cluster. 11 | 12 | The following is an example of using a `NamedMap` to `store`, `get`, and 13 | `remove` simple keys and values: 14 | 15 | .. literalinclude:: ../examples/basics.py 16 | :language: python 17 | :emphasize-lines: 17, 19, 21-34 18 | :linenos: 19 | 20 | * Line 17 - Create a new `Session` that will connect to `localhost:1408`. See the :doc:`Sessions ` documentation for more details. 21 | * Line 50 - Obtain a `NamedMap` identified by `my-map` from the Session 22 | * Lines 21-34 - Various CRUD operations against the NamedMap such as `get() `_, `put() `_, and `remove() `_ 23 | -------------------------------------------------------------------------------- /docs/caches.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2024, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Caches 7 | ====== 8 | 9 | Once a Session has been obtained, it is now possible to begin working with 10 | `NamedMap` and/or `NamedCache` instances. This is done by calling either 11 | `Session.get_map(name: str, cache_options: Optional[CacheOptions] = None)` or 12 | `Session.get_cache(name: str, cache_options: Optional[CacheOptions] = None)`. 13 | The `name` argument is the logical name for this cache. The optional `cache_options` 14 | argument accepts a `CacheOptions` to configure the default time-to-live (ttl) 15 | for entries placed in the cache as well as allowing the configuration of near 16 | caching (discussed later in this section). 17 | 18 | Here are some examples: 19 | 20 | .. code-block:: python 21 | 22 | # obtain NamedCache 'person' 23 | cache: NamedCache[int, Person] = await session.get_cache("person") 24 | 25 | .. code-block:: python 26 | 27 | # obtain NamedCache 'person' with a default ttl of 2000 millis 28 | # any entry inserted into this cache, unless overridden by a put call 29 | # with a custom ttl, will have a default ttl of 2000 30 | options: CacheOptions = CacheOptions(2000) 31 | cache: NamedCache[int, Person] = await session.get_cache("person", options) 32 | 33 | Near Caches 34 | =========== 35 | 36 | Near caches are a local cache within the `NamedMap` or `NamedCache` 37 | that will store entries as they are obtained from the remote cache. By doing 38 | so, it is possible to reduce the number of remote calls made to the Coherence 39 | cluster by returning the locally cached value. This local cache also 40 | ensures updates made to, or removal of, an entry are properly 41 | reflected thus ensuring stale data isn't mistakenly returned. 42 | 43 | .. note:: 44 | Near caching will only work with Coherence CE `25.03` or later. Attempting 45 | to use near caching features with older versions will have no effect. 46 | 47 | A near cache is configured via `NearCacheOptions` which provides several 48 | options for controlling how entries will be cached locally. 49 | 50 | - `ttl` - configures the time-to-live of locally cached entries (this has no 51 | impact on entries stored within Coherence). If not specified, or the 52 | `ttl` is `0`, entries in the near cache will not expire 53 | - `high_units` - configures the max number of entries that may be locally 54 | cached. Once the number of locally cached entries exceeds the configured 55 | value, the cache will be pruned down (least recently used entries first) 56 | to a target size based on the configured `prune_factor` 57 | (defaults to `0.80` meaning the prune operation would retain 80% of 58 | the entries) 59 | - `high_units_memory` - configures the maximum memory size, in bytes, the 60 | locally cached entries may use. If total memory exceeds the configured 61 | value, the cache will be pruned down (least recently used entries first) 62 | to a target size based on the configured `prune_factor` (defaults to 63 | `0.80` meaning the prune operation would retain 80% the cache memory) 64 | - `prune_factor` - configures the target near cache size after exceeding 65 | either `high_units` or `high_units_memory` high-water marks 66 | 67 | .. note:: 68 | `high_units` and `high_units_memory` are mutually exclusive 69 | 70 | Examples of configuring near caching: 71 | 72 | .. code-block:: python 73 | 74 | # obtain NamedCache 'person' and configure near caching with a local 75 | # ttl of 20_000 millis 76 | near_options: NearCacheOptions = NearCacheOptions(20_000) 77 | cache_options: CacheOptions = CacheOptions(near_cache_options=near_options) 78 | cache: NamedCache[int, Person] = await session.get_cache("person", options) 79 | 80 | 81 | .. code-block:: python 82 | 83 | # obtain NamedCache 'person' and configure near caching with a max 84 | # number of entries of 1_000 and when pruned, it will be reduced 85 | # to 20% 86 | near_options: NearCacheOptions = NearCacheOptions(high_units=1_000, prune_factor=0.20) 87 | cache_options: CacheOptions = CacheOptions(near_cache_options=near_options) 88 | cache: NamedCache[int, Person] = await session.get_cache("person", options) 89 | 90 | To verify the effectiveness of a near cache, several statistics are monitored 91 | and may be obtained from the `CacheStats` instance returned by the 92 | `near_cache_stats` property of the `NamedMap` or `NamedCache` 93 | 94 | The following statistics are available (the statistic name given is the same 95 | property name on the `CacheStats` instance) 96 | 97 | - hits - the number of times an entry was found in the near cache 98 | - misses - the number of times an entry was not found in the near cache 99 | - misses_duration - The accumulated time, in millis, spent for a cache miss 100 | (i.e., having to make a remote call and update the local cache) 101 | - hit_rate - the ratio of hits to misses 102 | - puts - the total number of puts that have been made against the near cache 103 | - gets - the total number of gets that have been made against the near cache 104 | - prunes - the number of times the cache was pruned due to exceeding the 105 | configured `high_units` or `high_units_memory` high-water marks 106 | - expires - the number of times the near cache's expiry logic expired entries 107 | - num_pruned - the total number of entries that were removed due to exceeding the 108 | configured `high_units` or `high_units_memory` high-water marks 109 | - num_expired - the total number of entries that were removed due to 110 | expiration 111 | - prunes_duration - the accumulated time, in millis, spent pruning 112 | the near cache 113 | - expires_duration - the accumulated time, in millis, removing 114 | expired entries from the near cache 115 | - size - the total number of entries currently held by the near cache 116 | - bytes - the total bytes the near cache entries consume 117 | 118 | .. note:: 119 | The `near_cache_stats` option will return `None` if near caching isn't 120 | configured or available 121 | 122 | The following example demonstrates the value that near caching can provide: 123 | 124 | .. literalinclude:: ../examples/near_caching.py 125 | :language: python 126 | :linenos: 127 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 3 | # Licensed under the Universal Permissive License v 1.0 as shown at 4 | # https://oss.oracle.com/licenses/upl. 5 | # 6 | # ---------------------------------------------------------------------------------------------------------------------- 7 | # Configuration file for the Sphinx documentation builder. 8 | # 9 | # This file only contains a selection of the most common options. For a full 10 | # list see the documentation: 11 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 12 | 13 | # -- Path setup -------------------------------------------------------------- 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | import sys 21 | 22 | print(os.path.abspath("../src")) 23 | sys.path.insert(0, os.path.abspath("../src")) 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = "2.0.2" 27 | 28 | # -- Project information ----------------------------------------------------- 29 | 30 | project = "Python Client for Oracle Coherence. v%s" % release 31 | copyright = "(c) 2022, 2025, Oracle and/or its affiliates." 32 | author = "Oracle" 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = ["m2r", "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_rtd_theme", "sphinx.ext.doctest"] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ["_templates"] 44 | 45 | # List of patterns, relative to source directory, that match files and 46 | # directories to ignore when looking for source files. 47 | # This pattern also affects html_static_path and html_extra_path. 48 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See the documentation for 54 | # a list of builtin themes. 55 | # 56 | html_theme = "sphinx_rtd_theme" 57 | 58 | # Add any paths that contain custom static files (such as style sheets) here, 59 | # relative to this directory. They are copied after the builtin static files, 60 | # so a file named "default.css" will overwrite the builtin "default.css". 61 | html_static_path = ["_static"] 62 | -------------------------------------------------------------------------------- /docs/entryprocessing.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Entry Processing 7 | ================ 8 | 9 | Entry processors are a defining feature of Coherence and provide the ability 10 | to send the data modification code into the grid and execute it where 11 | the data is, against one or more entries. This can not only significantly 12 | impact how much data needs to be moved over the wire, but it also takes care 13 | of cluster-wide concurrency control — each entry processor has the exclusive 14 | access to the entry it is processing for the duration of its execution. 15 | 16 | See the utility class `Processors `_ for the 17 | entry processors supported out-of-the-box by this client. 18 | 19 | The following example demonstrates various entry processing operations 20 | against a `NamedMap`: 21 | 22 | .. literalinclude:: ../examples/processors.py 23 | :language: python 24 | :emphasize-lines: 37, 44, 51, 57-58, 62 25 | :linenos: 26 | 27 | * Line 37 - insert a new Hobbit into the `NamedMap` 28 | * Line 44 - invoke an entry processor to update the age of the inserted Hobbit 29 | * Line 51 - insert a second Hobbit into the `NamedMap` 30 | * Lines 57 - 58 - Increment the ages of all Hobbits in the `NamedMap` by 10. Store the keys of the updated Hobbits 31 | * Line 62 - get all of the updated Hobbits to display 32 | -------------------------------------------------------------------------------- /docs/events.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Events 7 | ====== 8 | 9 | Coherence provides the ability to subscribe to notifications pertaining to 10 | a particular map/cache. In addition to listening for specific 11 | events, it is possible to listen to events for changes made to a specific 12 | key, or using a Filter, it's possible to limit the events raised to be 13 | for a subset of the map entries. 14 | 15 | The following example demonstrates using lifecycle and map events. 16 | 17 | .. literalinclude:: ../examples/events.py 18 | :language: python 19 | :emphasize-lines: 25-39, 45-46, 50-54, 60-66, 71-77 20 | :linenos: 21 | 22 | * Lines 25-39 - Using `NamedMap.on() `_, 23 | define listeners supported events and pass in a lambda to print the invocation. 24 | Then one by one, trigger each of the events and ensure enough time is given 25 | for the event to occur. 26 | * Lines 45-46 - Create a new `MapListener `_ 27 | and for any event, print the result. 28 | * Lines 50-54 - Add the `MapListener` that will be triggered on all events 29 | For this section, events will be printed for inserted, updated, and deleted. 30 | * Lines 60-66 - Add the `MapListener` that will be triggered when any entry 31 | is inserted. For this section, only the inserted event will be printed. 32 | * 71-77 - Add the `MapListener` that will be triggered when an entry's value's 33 | length is greater than 1 and only for updated and removed events. For this 34 | section, only updated and deleted events will be printed once the loop progresses 35 | enough to insert values larger than "9" 36 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :hidden: 9 | 10 | installation 11 | sessions 12 | caches 13 | basics 14 | querying 15 | aggregation 16 | entryprocessing 17 | events 18 | serialization 19 | api_reference 20 | 21 | .. mdinclude:: ../README.md 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Installation 7 | ============ 8 | 9 | To install the Coherence Python Client one would need to do a pip install of the coherence package as shown below: 10 | 11 | .. code-block:: bash 12 | 13 | python3 -m pip install coherence-client 14 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | :: --------------------------------------------------------------------------------------------------------------------- 2 | :: Copyright (c) 2022, Oracle and/or its affiliates. 3 | :: Licensed under the Universal Permissive License v 1.0 as shown at 4 | :: https://oss.oracle.com/licenses/upl. 5 | :: 6 | :: --------------------------------------------------------------------------------------------------------------------- 7 | @ECHO OFF 8 | 9 | pushd %~dp0 10 | 11 | REM Command file for Sphinx documentation 12 | 13 | if "%SPHINXBUILD%" == "" ( 14 | set SPHINXBUILD=sphinx-build 15 | ) 16 | set SOURCEDIR=. 17 | set BUILDDIR=_build 18 | 19 | %SPHINXBUILD% >NUL 2>NUL 20 | if errorlevel 9009 ( 21 | echo. 22 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 23 | echo.installed, then set the SPHINXBUILD environment variable to point 24 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 25 | echo.may add the Sphinx directory to PATH. 26 | echo. 27 | echo.If you don't have Sphinx installed, grab it from 28 | echo.https://www.sphinx-doc.org/ 29 | exit /b 1 30 | ) 31 | 32 | if "%1" == "" goto help 33 | 34 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 35 | goto end 36 | 37 | :help 38 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 39 | 40 | :end 41 | popd 42 | -------------------------------------------------------------------------------- /docs/querying.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2023, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Querying 7 | ======== 8 | 9 | Being able to store and access data based on a key is great, but sometimes 10 | you need more than that. Coherence allows you to query on any data attribute 11 | present in your data model, and even allows you to define your own query 12 | filter if one of the built-in ones doesn't fit the bill. If you defined an 13 | index for a given query attribute, it will be used to optimize the query 14 | execution and avoid unnecessary deserialization. 15 | 16 | See the utility class `Filters `_ for the 17 | filters supported out-of-the-box by this client. 18 | 19 | The following example demonstrates various querying operations 20 | against a `NamedMap`: 21 | 22 | .. literalinclude:: ../examples/filters.py 23 | :language: python 24 | :emphasize-lines: 40-41, 46, 50, 55 25 | :linenos: 26 | 27 | * Lines 40-41 - insert twenty random Hobbits 28 | * Line 46 - find all Hobbits, including their associated key, between the age of 17 and 21 29 | * Line 50 - find all Hobbits, including their associated key, between the age of 17 and 30 that live in Hobbiton 30 | * Line 55 - find all Hobbits, including their associated key, between the age of 17 and 25 that live in Hobbiton or Frogmorton 31 | -------------------------------------------------------------------------------- /docs/sessions.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (c) 2022, 2024, Oracle and/or its affiliates. 3 | Licensed under the Universal Permissive License v 1.0 as shown at 4 | https://oss.oracle.com/licenses/upl. 5 | 6 | Sessions 7 | ======== 8 | 9 | Coherence uses the concept of a `Session` to manage a set of related Coherence resources, 10 | such as maps and/or caches. When using the Coherence Python Client, a `Session` connects to a specific 11 | gRPC endpoint and uses a specific serialization format to marshal requests and responses. 12 | This means that different sessions using different serializers may connect to the same server endpoint. Typically, 13 | for efficiency the client and server would be configured to use matching serialization formats to avoid 14 | deserialization of data on the server, but this does not have to be the case. If the server is using a different 15 | serializer for the server-side caches, it must be able to deserialize the client's requests, so there must be 16 | a serializer configured on the server to match that used by the client. 17 | 18 | .. note:: 19 | Currently, the Coherence Python client only supports JSON serialization 20 | 21 | A `Session` is constructed using an `Options` instance, or a generic object with the same keys and values. 22 | 23 | The currently supported arguments foe `Options` are: 24 | - `address` - the address of the Coherence gRPC proxy. This defaults to `localhost:1408`. 25 | - `request_timeout_seconds` - the gRPC request timeout in seconds. This defaults to `30.0`. 26 | - `channel_options` - per-request gRPC channel options. 27 | - `tls_options` - options related to the configuration of TLS. 28 | 29 | - `enabled` - determines if TLS is enabled or not. This defaults to `false` (NOTE: assumes `true` if all three `COHERENCE_TLS_*` (see subsequent bullets) environment variables are defined) 30 | - `ca_cert_path` - the path to the CA certificate. This may be configured using the environment variable `COHERENCE_TLS_CERTS_PATH` 31 | - `client_cert_path` - the path to the client certificate. This may be configured with the environment variable `COHERENCE_TLS_CLIENT_CERT` 32 | - `client_key_path` - the path to the client certificate key. This may be configured with the environment variable `COHERENCE_TLS_CLIENT_KEY` 33 | 34 | .. code-block:: python 35 | 36 | from coherence import NamedCache, Session 37 | import asyncio 38 | 39 | # create a new Session to the Coherence server 40 | session: Session = await Session.create() 41 | 42 | This is the simplest invocation which assumes the following defaults: 43 | - `address` is `localhost:1408` 44 | - `request_timeout_seconds` is `30.0` 45 | - `tls_options` is `disabled` 46 | 47 | To use values other than the default, create a new `Options` instance, configure as desired, 48 | and pass it to the constructor of the `Session`: 49 | 50 | .. code-block:: python 51 | 52 | from coherence import NamedCache, Session 53 | import asyncio 54 | 55 | # create a new Session to the Coherence server 56 | addr: str = 'example.com:4444' 57 | opt: Options = Options(addr, default_scope, default_request_timeout, default_format) 58 | session: Session = await Session.create(opt) 59 | 60 | It's also possible to control the default address the session will bind to by providing 61 | an address via the `COHERENCE_SERVER_ADDRESS` environment variable. The format of the value would 62 | be the same as if you configured it programmatically as the above example shows. The default timeout 63 | can also be configured using `COHERENCE_CLIENT_REQUEST_TIMEOUT` environment variable. 64 | 65 | Once the session has been constructed, it will now be possible to create maps and caches. 66 | -------------------------------------------------------------------------------- /etc/proto/common_messages_v1.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, 2024, Oracle and/or its affiliates. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl. 6 | */ 7 | 8 | // ----------------------------------------------------------------- 9 | // Common messages used by various Coherence services. 10 | // ----------------------------------------------------------------- 11 | 12 | syntax = "proto3"; 13 | 14 | package coherence.common.v1; 15 | 16 | import "google/protobuf/any.proto"; 17 | 18 | option java_multiple_files = true; 19 | option java_package = "com.oracle.coherence.grpc.messages.common.v1"; 20 | 21 | // An error message 22 | message ErrorMessage { 23 | // The text of the error message 24 | string message = 1; 25 | // An optional Exception serialized using the client's serializer 26 | optional bytes error = 2; 27 | } 28 | 29 | // A message to indicate completion of a request response. 30 | message Complete { 31 | } 32 | 33 | // A heart beat message. 34 | message HeartbeatMessage { 35 | // The UUID of the client 36 | optional bytes uuid = 1; 37 | // True to send a heartbeat response 38 | bool ack = 2; 39 | } 40 | 41 | // An optional value. 42 | message OptionalValue { 43 | // A flag indicating whether the value is present. 44 | bool present = 1; 45 | // The serialized value. 46 | bytes value = 2; 47 | } 48 | 49 | // A message that contains a collection of serialized binary values. 50 | message CollectionOfBytesValues { 51 | // The serialized values 52 | repeated bytes values = 1; 53 | } 54 | 55 | // A message containing a serialized key and value. 56 | message BinaryKeyAndValue { 57 | // The serialized binary key. 58 | bytes key = 1; 59 | // The serialized binary value. 60 | bytes value = 2; 61 | } 62 | -------------------------------------------------------------------------------- /etc/proto/proxy_service_messages_v1.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, 2024, Oracle and/or its affiliates. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl. 6 | */ 7 | 8 | syntax = "proto3"; 9 | 10 | package coherence.proxy.v1; 11 | 12 | import "common_messages_v1.proto"; 13 | import "google/protobuf/any.proto"; 14 | 15 | option java_multiple_files = true; 16 | option java_package = "com.oracle.coherence.grpc.messages.proxy.v1"; 17 | 18 | // ----------------------------------------------------------------- 19 | // Messages used by the Coherence gRPC Proxy Service. 20 | // ----------------------------------------------------------------- 21 | 22 | // A request to the Coherence gRPC proxy. 23 | // Except for a Heartbeat, every request must have a unique id field. 24 | message ProxyRequest { 25 | int64 id = 1; 26 | oneof request { 27 | // The initialization request, which must be the first request sent. 28 | InitRequest init = 3; 29 | // A message that is specific to a Coherence gRPC service. 30 | // Each service on the proxy will know what type to expect here. 31 | google.protobuf.Any message = 4; 32 | // A periodic heartbeat message sent by the client 33 | coherence.common.v1.HeartbeatMessage heartbeat = 5; 34 | } 35 | } 36 | 37 | // A response from a Coherence gRPC proxy. 38 | // Except for a Heartbeat, every response will contain an id field 39 | // that corresponds to the id of the request that the response if for. 40 | message ProxyResponse { 41 | // The identifier of the request messages this response is for, or zero if 42 | // this message is non-request related, for example it is an event. 43 | int64 id = 1; 44 | // The actual response message. 45 | oneof response { 46 | // The response to the initial InitRequest. 47 | InitResponse init = 4; 48 | // A response of a type specific to a Coherence gRPC service. 49 | // The client that sent the corresponding request will know what 50 | // type of message it expects in this field. 51 | google.protobuf.Any message = 5; 52 | // An error response to a specific request id 53 | coherence.common.v1.ErrorMessage error = 6; 54 | // A complete message is sent to indicate that a stream of messages for 55 | // the same request id have been completed. 56 | coherence.common.v1.Complete complete = 7; 57 | // A periodic heart beat sent by the server 58 | coherence.common.v1.HeartbeatMessage heartbeat = 8; 59 | } 60 | } 61 | 62 | // Initialize a connection. 63 | message InitRequest { 64 | // The scope name to use to obtain the server resources. 65 | string scope = 2; 66 | // The serialization format to use. 67 | string format = 3; 68 | // The protocol to use for the channel 69 | string protocol = 4; 70 | // The protocol version requested by the client 71 | int32 protocolVersion = 5; 72 | // The minimum protocol version supported by the client 73 | int32 supportedProtocolVersion = 6; 74 | // The requested frequency that heartbeat messages should be sent by the server (in millis) 75 | optional int64 heartbeat = 7; 76 | // The optional client UUID (usually from Coherence clients that have a local Member UUID). 77 | optional bytes clientUuid = 8; 78 | } 79 | 80 | // The response to an InitRequest 81 | message InitResponse { 82 | // This client connection's UUID. 83 | bytes uuid = 1; 84 | // The Coherence version of the proxy 85 | string version = 2; 86 | // The encoded version of the proxy 87 | int32 encodedVersion = 3; 88 | // The protocol version the client should use 89 | int32 protocolVersion = 4; 90 | // The proxy member Id 91 | int32 proxyMemberId = 5; 92 | // The proxy member UUID 93 | bytes proxyMemberUuid = 6; 94 | } 95 | -------------------------------------------------------------------------------- /etc/proto/proxy_service_v1.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, 2024, Oracle and/or its affiliates. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl. 6 | */ 7 | 8 | // NamedCacheService V2 service definition. 9 | 10 | syntax = "proto3"; 11 | 12 | package coherence.proxy.v1; 13 | 14 | import "proxy_service_messages_v1.proto"; 15 | import "google/protobuf/empty.proto"; 16 | import "google/protobuf/wrappers.proto"; 17 | 18 | option java_multiple_files = true; 19 | option java_package = "com.oracle.coherence.grpc.services.proxy.v1"; 20 | 21 | // ----------------------------------------------------------------- 22 | // The Coherence gRPC Proxy Service definition. 23 | // ----------------------------------------------------------------- 24 | 25 | service ProxyService { 26 | // Sets up a bidirectional channel for cache requests and responses. 27 | rpc subChannel (stream ProxyRequest) returns (stream ProxyResponse) { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /etc/proto/services.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Oracle and/or its affiliates. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * http://oss.oracle.com/licenses/upl. 6 | */ 7 | 8 | // Authors: 9 | // Mahesh Kannan 10 | // Jonathan Knight 11 | 12 | // NamedCacheService service definition. 13 | 14 | syntax = "proto3"; 15 | 16 | package coherence; 17 | 18 | import "messages.proto"; 19 | import "google/protobuf/empty.proto"; 20 | import "google/protobuf/wrappers.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_package = "com.oracle.coherence.grpc"; 24 | 25 | // A gRPC NamedCache service. 26 | // 27 | service NamedCacheService { 28 | 29 | // Add an index to a cache. 30 | rpc addIndex (AddIndexRequest) returns (google.protobuf.Empty) { 31 | } 32 | 33 | // Obtain the results of running an entry aggregator against the cache. 34 | // The aggregator may run against entries specified by key or entries 35 | // matching a given filter. 36 | rpc aggregate (AggregateRequest) returns (google.protobuf.BytesValue) { 37 | } 38 | 39 | // Clear a cache. 40 | rpc clear (ClearRequest) returns (google.protobuf.Empty) { 41 | } 42 | 43 | // Check if this map contains a mapping for the specified key to the specified value. 44 | rpc containsEntry (ContainsEntryRequest) returns (google.protobuf.BoolValue) { 45 | } 46 | 47 | // Check if this map contains a mapping for the specified key. 48 | rpc containsKey (ContainsKeyRequest) returns (google.protobuf.BoolValue) { 49 | } 50 | 51 | // Check if this map contains a mapping for the specified value. 52 | rpc containsValue (ContainsValueRequest) returns (google.protobuf.BoolValue) { 53 | } 54 | 55 | // Destroy a cache. 56 | rpc destroy (DestroyRequest) returns (google.protobuf.Empty) { 57 | } 58 | 59 | // Obtain all of the entries in the cache where the cache entries 60 | // match a given filter. 61 | rpc entrySet (EntrySetRequest) returns (stream Entry) { 62 | } 63 | 64 | // Sets up a bidirectional channel for cache events. 65 | rpc events (stream MapListenerRequest) returns (stream MapListenerResponse) { 66 | } 67 | 68 | // Get a value for a given key from a cache. 69 | rpc get (GetRequest) returns (OptionalValue) { 70 | } 71 | 72 | // Get all of the values from a cache for a given collection of keys. 73 | rpc getAll (GetAllRequest) returns (stream Entry) { 74 | } 75 | 76 | // Invoke an entry processor against an entry in a cache. 77 | rpc invoke (InvokeRequest) returns (google.protobuf.BytesValue) { 78 | } 79 | 80 | // Invoke an entry processor against a number of entries in a cache. 81 | rpc invokeAll (InvokeAllRequest) returns (stream Entry) { 82 | } 83 | 84 | // Determine whether a cache is empty. 85 | rpc isEmpty (IsEmptyRequest) returns (google.protobuf.BoolValue) { 86 | } 87 | 88 | // Obtain all of the keys in the cache where the cache entries 89 | // match a given filter. 90 | rpc keySet (KeySetRequest) returns (stream google.protobuf.BytesValue) { 91 | } 92 | 93 | // Get the next page of a paged entry set request. 94 | rpc nextEntrySetPage (PageRequest) returns (stream EntryResult) { 95 | } 96 | 97 | // Get the next page of a paged key set request. 98 | rpc nextKeySetPage (PageRequest) returns (stream google.protobuf.BytesValue) { 99 | } 100 | 101 | // Associate the specified value with the specified key in this cache. 102 | // If the cache previously contained a mapping for the key, the old value 103 | // is replaced by the specified value. 104 | // An optional expiry (TTL) value may be set for the entry to expire the 105 | // entry from the cache after that time. 106 | rpc put (PutRequest) returns (google.protobuf.BytesValue) { 107 | } 108 | 109 | // Copies all of the mappings from the request into the cache. 110 | rpc putAll (PutAllRequest) returns (google.protobuf.Empty) { 111 | } 112 | 113 | // If the specified key is not already associated with a value (or is mapped 114 | // to null associate it with the given value and returns null, else return 115 | // the current value. 116 | rpc putIfAbsent (PutIfAbsentRequest) returns (google.protobuf.BytesValue) { 117 | } 118 | 119 | // Remove the mapping that is associated with the specified key. 120 | rpc remove (RemoveRequest) returns (google.protobuf.BytesValue) { 121 | } 122 | 123 | // Remove an index from the cache. 124 | rpc removeIndex (RemoveIndexRequest) returns (google.protobuf.Empty) { 125 | } 126 | 127 | // Remove the mapping that is associated with the specified key only 128 | // if the mapping exists in the cache. 129 | rpc removeMapping (RemoveMappingRequest) returns (google.protobuf.BoolValue) { 130 | } 131 | 132 | // Replace the entry for the specified key only if it is currently 133 | // mapped to some value. 134 | rpc replace (ReplaceRequest) returns (google.protobuf.BytesValue) { 135 | } 136 | 137 | // Replace the mapping for the specified key only if currently mapped 138 | // to the specified value. 139 | rpc replaceMapping (ReplaceMappingRequest) returns (google.protobuf.BoolValue) { 140 | } 141 | 142 | // Determine the number of entries in a cache. 143 | rpc size (SizeRequest) returns (google.protobuf.Int32Value) { 144 | } 145 | 146 | // Truncate a cache. This is the same as clearing a cache but no 147 | // cache entry events will be generated. 148 | rpc truncate (TruncateRequest) returns (google.protobuf.Empty) { 149 | } 150 | 151 | // Obtain all of the values in the cache where the cache entries 152 | // match a given filter. 153 | rpc values (ValuesRequest) returns (stream google.protobuf.BytesValue) { 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ### Coherence Python Client Examples 2 | 3 | All examples in this directory assume that the Coherence Python Client has 4 | been installed. 5 | 6 | ```bash 7 | python3 -m pip install coherence-client 8 | ``` 9 | 10 | Be sure a Coherence gRPC proxy is available for the examples to work against. 11 | 12 | ```bash 13 | docker run -d -p 1408:1408 ghcr.io/oracle/coherence-ce:22.06.11 14 | ``` 15 | 16 | > [!NOTE] 17 | > Coherence AI [vector_search.py](vector_search.py) example requires installation of `sentence-transformers` package so that the example code can use the `all-MiniLM-L6-v2` model for generating text embeddings 18 | > 19 | > ```bash 20 | > python3 -m pip install sentence-transformers 21 | > ``` 22 | 23 | 24 | ### The Examples 25 | * [basics.py](basics.py) - basic CRUD operations 26 | * [python_object_keys_and_values.py](python_object_keys_and_values.py) - shows how to use standard Python objects as keys or values of a cache 27 | * [filters.py](filters.py) - using filters to filter results 28 | * [processors.py](processors.py) - using entry processors to mutate cache entries on the server without get/put 29 | * [aggregators.py](aggregators.py) - using entry aggregators to query a subset of entries to produce a result 30 | * [events.py](events.py) - demonstrates cache lifecycle and cache entry events 31 | * [vector_search.py](vector_search.py) - shows how to use some of the Coherence AI features to store vectors and perform a k-nearest neighbors (k-nn) search on those vectors. 32 | -------------------------------------------------------------------------------- /examples/aggregators.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | from dataclasses import dataclass 7 | from decimal import Decimal 8 | from typing import Dict, List 9 | 10 | from coherence import Aggregators, Filters, NamedMap, Session 11 | 12 | 13 | @dataclass 14 | class Hobbit: 15 | """ 16 | A simple class representing a Hobbit. 17 | """ 18 | 19 | id: int 20 | name: str 21 | age: int 22 | hobbies: str 23 | 24 | 25 | async def do_run() -> None: 26 | """ 27 | Demonstrates various Aggregator operations against a NamedMap. 28 | 29 | :return: None 30 | """ 31 | person_data = { 32 | 1: Hobbit(1, "Bilbo", 111, "Burglaring"), 33 | 2: Hobbit(2, "Frodo", 50, "Bearing"), 34 | 3: Hobbit(3, "Sam", 38, "Side Kick"), 35 | 4: Hobbit(3, "Meriadoc", 36, "Side Kick"), 36 | 5: Hobbit(3, "Peregrin", 28, "Side Kick"), 37 | } 38 | 39 | session: Session = await Session.create() 40 | try: 41 | named_map: NamedMap[int, Hobbit] = await session.get_map("aggregation-test") 42 | 43 | await named_map.clear() 44 | 45 | await named_map.put_all(person_data) 46 | 47 | distinct_hobbies: List[str] = await named_map.aggregate(Aggregators.distinct("hobbies")) 48 | print("Distinct hobbies :", distinct_hobbies) 49 | 50 | count: int = await named_map.aggregate(Aggregators.count()) 51 | print("Number of Hobbits :", count) 52 | 53 | over_forty: int = await named_map.aggregate(Aggregators.count(), filter=Filters.greater("age", 40)) 54 | print("Number of Hobbits older than 40 :", over_forty) 55 | 56 | avg_under_forty: Decimal = await named_map.aggregate(Aggregators.average("age"), filter=Filters.less("age", 40)) 57 | print("Average age of Hobbits under 40 :", int(avg_under_forty)) 58 | 59 | print("The oldest Hobbit for each hobby ...") 60 | results: Dict[str, int] = await named_map.aggregate(Aggregators.group_by("hobbies", Aggregators.max("age"))) 61 | for hobby, age in results.items(): 62 | print("Hobby: ", hobby, "Max age: ", age) 63 | finally: 64 | await session.close() 65 | 66 | 67 | asyncio.run(do_run()) 68 | -------------------------------------------------------------------------------- /examples/basics.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | 7 | from coherence import NamedMap, Session 8 | 9 | 10 | async def do_run() -> None: 11 | """ 12 | Demonstrates basic CRUD operations against a NamedMap using 13 | `int` keys and `str` values. 14 | 15 | :return: None 16 | """ 17 | session: Session = await Session.create() 18 | try: 19 | named_map: NamedMap[int, str] = await session.get_map("my-map") 20 | 21 | print("Put key 1; value one") 22 | await named_map.put(1, "one") 23 | 24 | print("Value for key 1 is :", await named_map.get(1)) 25 | 26 | print("NamedMap size is :", await named_map.size()) 27 | 28 | print("Updating value of key 1 to ONE from ", await named_map.put(1, "ONE")) 29 | 30 | print("Value for key 1 is :", await named_map.get(1)) 31 | 32 | print("Removing key 1, current value :", await named_map.remove(1)) 33 | 34 | print("NamedMap size is :", await named_map.size()) 35 | finally: 36 | await session.close() 37 | 38 | 39 | asyncio.run(do_run()) 40 | -------------------------------------------------------------------------------- /examples/events.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | 7 | from coherence import Filters, NamedMap, Session 8 | from coherence.event import MapLifecycleEvent, MapListener 9 | from coherence.filter import MapEventFilter 10 | 11 | 12 | async def do_run() -> None: 13 | """ 14 | Demonstrates listeners for entry events and cache lifecycle. 15 | 16 | :return: None 17 | """ 18 | session: Session = await Session.create() 19 | try: 20 | named_map: NamedMap[int, str] = await session.get_map("listeners-map") 21 | await named_map.put(1, "1") 22 | 23 | print("NamedMap lifecycle events") 24 | 25 | named_map.on(MapLifecycleEvent.RELEASED, lambda x: print("RELEASED", x)) 26 | named_map.on(MapLifecycleEvent.TRUNCATED, lambda x: print("TRUNCATE", x)) 27 | named_map.on(MapLifecycleEvent.DESTROYED, lambda x: print("DESTROYED", x)) 28 | 29 | print("Truncating the NamedMap; this should generate an event ...") 30 | await named_map.truncate() 31 | await asyncio.sleep(1) 32 | 33 | print("Releasing the NamedMap; this should generate an event ...") 34 | await named_map.release() 35 | await asyncio.sleep(1) 36 | 37 | print("Destroying the NamedMap; this should generate an event ...") 38 | await named_map.destroy() 39 | await asyncio.sleep(1) 40 | 41 | print("\n\nNamedMap entry events") 42 | 43 | named_map = await session.get_map("listeners-map") 44 | 45 | listener1: MapListener[int, str] = MapListener() 46 | listener1.on_any(lambda e: print(e)) 47 | 48 | print("Added listener for all events") 49 | print("Events will be generated when an entry is inserted, updated, and removed") 50 | await named_map.add_map_listener(listener1) 51 | 52 | await named_map.put(1, "1") 53 | await named_map.put(1, "2") 54 | await named_map.remove(1) 55 | await asyncio.sleep(1) 56 | 57 | await named_map.remove_map_listener(listener1) 58 | 59 | print("\nAdded listener for all entries, but only when they are inserted") 60 | ins_filter = Filters.event(Filters.always(), MapEventFilter.INSERTED) 61 | await named_map.add_map_listener(listener1, ins_filter) 62 | 63 | await named_map.put(1, "1") 64 | await named_map.put(1, "2") 65 | await named_map.remove(1) 66 | await asyncio.sleep(1) 67 | 68 | await named_map.remove_map_listener(listener1, ins_filter) 69 | 70 | print("\nAdded listener for entries with a length larger than one, but only when they are updated or removed") 71 | upd_del_filter = Filters.event(Filters.greater("length()", 1), MapEventFilter.UPDATED | MapEventFilter.DELETED) 72 | await named_map.add_map_listener(listener1, upd_del_filter) 73 | 74 | for i in range(12): 75 | await named_map.put(i, str(i)) 76 | await named_map.put(i, str(i + 1)) 77 | await named_map.remove(i) 78 | 79 | await asyncio.sleep(1) 80 | 81 | await named_map.remove_map_listener(listener1, upd_del_filter) 82 | await named_map.clear() 83 | finally: 84 | await session.close() 85 | 86 | 87 | asyncio.run(do_run()) 88 | -------------------------------------------------------------------------------- /examples/filters.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | from dataclasses import dataclass 7 | from typing import List 8 | 9 | from coherence import Filters, NamedMap, Session 10 | from coherence.filter import Filter 11 | 12 | 13 | @dataclass 14 | class Hobbit: 15 | """ 16 | A simple class representing a Hobbit. 17 | """ 18 | 19 | id: int 20 | name: str 21 | age: int 22 | home: str 23 | 24 | 25 | async def do_run() -> None: 26 | """ 27 | Demonstrates various Filter operations against a NamedMap. 28 | 29 | :return: None 30 | """ 31 | session: Session = await Session.create() 32 | try: 33 | homes: List[str] = ["Hobbiton", "Buckland", "Frogmorton", "Stock"] 34 | named_map: NamedMap[int, Hobbit] = await session.get_map("hobbits") 35 | 36 | await named_map.clear() 37 | 38 | num_hobbits: int = 20 39 | print("Adding", num_hobbits, "random Hobbits ...") 40 | for i in range(num_hobbits): 41 | await named_map.put(i, Hobbit(i, "Hobbit-" + str(i), 15 + i, homes[i % 4])) 42 | 43 | print("NamedMap size is :", await named_map.size()) 44 | 45 | print("Retrieve the Hobbits between the ages of 17 and 21 ...") 46 | async for entry in await named_map.entries(Filters.between("age", 17, 21)): 47 | print("Key :", entry.key, ", Value :", entry.value) 48 | 49 | print("Retrieve the Hobbits between the ages of 17 and 30 and live in Hobbiton ...") 50 | query_filter: Filter = Filters.between("age", 17, 30).And(Filters.equals("home", "Hobbiton")) 51 | async for entry in await named_map.entries(query_filter): 52 | print("Key :", entry.key, ", Value :", entry.value) 53 | 54 | print("Retrieve the Hobbits between the ages of 17 and 25 who live in Hobbiton or Frogmorton") 55 | query_filter = Filters.between("age", 17, 25).And(Filters.is_in("home", {"Hobbiton", "Frogmorton"})) 56 | async for entry in await named_map.entries(query_filter): 57 | print("Key :", entry.key, ", Value :", entry.value) 58 | 59 | finally: 60 | await session.close() 61 | 62 | 63 | asyncio.run(do_run()) 64 | -------------------------------------------------------------------------------- /examples/movies.json.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oracle/coherence-py-client/01d0229d11d9680d4b26af35d844b1a62db8de62/examples/movies.json.gzip -------------------------------------------------------------------------------- /examples/near_caching.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | import time 7 | from functools import reduce 8 | 9 | from coherence import CacheOptions, CacheStats, NamedCache, NearCacheOptions, Session 10 | 11 | 12 | async def do_run() -> None: 13 | session: Session = await Session.create() 14 | 15 | # obtain the basic cache and configure near caching with 16 | # all defaults; meaning no expiry or pruning 17 | cache_remote: NamedCache[str, str] = await session.get_cache("remote") 18 | cache_near: NamedCache[str, str] = await session.get_cache( 19 | "near", CacheOptions(near_cache_options=NearCacheOptions(ttl=0)) 20 | ) 21 | 22 | await cache_remote.clear() 23 | await cache_near.clear() 24 | stats: CacheStats = cache_near.near_cache_stats 25 | 26 | # these knobs control: 27 | # - how many current tasks to run 28 | # - how many entries will be inserted and queried 29 | # - how many times the calls will be invoked 30 | task_count: int = 25 31 | num_entries: int = 1_000 32 | iterations: int = 4 33 | 34 | # seed data to populate the cache 35 | cache_seed: dict[str, str] = {str(x): str(x) for x in range(num_entries)} 36 | cache_seed_keys: set[str] = {key for key in cache_seed.keys()} 37 | print() 38 | 39 | # task calling get_all() for 1_000 keys 40 | async def get_all_task(task_cache: NamedCache[str, str]) -> int: 41 | begin = time.time_ns() 42 | 43 | for _ in range(iterations): 44 | async for _ in await task_cache.get_all(cache_seed_keys): 45 | continue 46 | 47 | return (time.time_ns() - begin) // 1_000_000 48 | 49 | await cache_remote.put_all(cache_seed) 50 | await cache_near.put_all(cache_seed) 51 | 52 | print("Run without near caching ...") 53 | begin_outer: int = time.time_ns() 54 | results: list[int] = await asyncio.gather(*[get_all_task(cache_remote) for _ in range(task_count)]) 55 | end_outer: int = time.time_ns() 56 | total_time = end_outer - begin_outer 57 | task_time = reduce(lambda first, second: first + second, results) 58 | 59 | # Example output 60 | # Run without near caching ... 61 | # [remote] 25 tasks completed! 62 | # [remote] Total time: 4246ms 63 | # [remote] Tasks completion average: 3755.6 64 | 65 | print(f"[remote] {task_count} tasks completed!") 66 | print(f"[remote] Total time: {total_time // 1_000_000}ms") 67 | print(f"[remote] Tasks completion average: {task_time / task_count}") 68 | 69 | print() 70 | print("Run with near caching ...") 71 | begin_outer = time.time_ns() 72 | results = await asyncio.gather(*[get_all_task(cache_near) for _ in range(task_count)]) 73 | end_outer = time.time_ns() 74 | total_time = end_outer - begin_outer 75 | task_time = reduce(lambda first, second: first + second, results) 76 | 77 | # Run with near caching ... 78 | # [near] 25 tasks completed! 79 | # [near] Total time: 122ms 80 | # [near] Tasks completion average: 113.96 81 | # [near] Near cache statistics: CacheStats(puts=1000, gets=100000, hits=99000, 82 | # misses=1000, misses-duration=73ms, hit-rate=0.99, prunes=0, 83 | # num-pruned=0, prunes-duration=0ms, size=1000, expires=0, 84 | # num-expired=0, expires-duration=0ms, memory-bytes=681464) 85 | 86 | print(f"[near] {task_count} tasks completed!") 87 | print(f"[near] Total time: {total_time // 1_000_000}ms") 88 | print(f"[near] Tasks completion average: {task_time / task_count}") 89 | print(f"[near] Near cache statistics: {stats}") 90 | 91 | await session.close() 92 | 93 | 94 | asyncio.run(do_run()) 95 | -------------------------------------------------------------------------------- /examples/processors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | from dataclasses import dataclass 7 | from typing import List 8 | 9 | from coherence import NamedMap, Processors, Session 10 | 11 | 12 | @dataclass 13 | class Hobbit: 14 | """ 15 | A simple class representing a Hobbit. 16 | """ 17 | 18 | id: int 19 | name: str 20 | age: int 21 | 22 | 23 | async def do_run() -> None: 24 | """ 25 | Demonstrates various EntryProcessor operations against a NamedMap. 26 | 27 | :return: None 28 | """ 29 | session: Session = await Session.create() 30 | try: 31 | named_map: NamedMap[int, Hobbit] = await session.get_map("hobbits") 32 | 33 | await named_map.clear() 34 | 35 | hobbit: Hobbit = Hobbit(1, "Bilbo", 111) 36 | print("Add new hobbit :", hobbit) 37 | await named_map.put(hobbit.id, hobbit) 38 | 39 | print("NamedMap size is :", await named_map.size()) 40 | 41 | print("Hobbit from get() :", await named_map.get(hobbit.id)) 42 | 43 | print("Update Hobbit using processor ...") 44 | await named_map.invoke(hobbit.id, Processors.update("age", 112)) 45 | 46 | print("Updated Hobbit is :", await named_map.get(hobbit.id)) 47 | 48 | hobbit2: Hobbit = Hobbit(2, "Frodo", 50) 49 | 50 | print("Add new hobbit :", hobbit2) 51 | await named_map.put(hobbit2.id, hobbit2) 52 | 53 | print("NamedMap size is :", await named_map.size()) 54 | 55 | print("Sending all Hobbits ten years into the future!") 56 | keys: List[int] = [] 57 | async for entry in await named_map.invoke_all(Processors.increment("age", 10)): 58 | keys.append(entry.key) 59 | print("Updated age of Hobbit with id ", entry.key, "to", entry.value) 60 | 61 | print("Displaying all updated Hobbits ...") 62 | async for result in await named_map.get_all(set(keys)): 63 | print(result.value) 64 | 65 | await named_map.remove(hobbit.id) 66 | await named_map.remove(hobbit2.id) 67 | finally: 68 | await session.close() 69 | 70 | 71 | asyncio.run(do_run()) 72 | -------------------------------------------------------------------------------- /examples/python_object_keys_and_values.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import asyncio 6 | from dataclasses import dataclass 7 | 8 | from coherence import NamedMap, Processors, Session 9 | 10 | 11 | @dataclass 12 | class AccountKey: 13 | account_id: int 14 | account_type: str 15 | 16 | 17 | @dataclass 18 | class Account: 19 | account_id: int 20 | account_type: str 21 | name: str 22 | balance: float 23 | 24 | 25 | async def do_run() -> None: 26 | """ 27 | Demonstrates basic CRUD operations against a NamedMap using 28 | `AccountKey` keys with `Account` values. 29 | 30 | :return: None 31 | """ 32 | session: Session = await Session.create() 33 | try: 34 | named_map: NamedMap[AccountKey, Account] = await session.get_map("accounts") 35 | 36 | await named_map.clear() 37 | 38 | new_account_key: AccountKey = AccountKey(100, "savings") 39 | new_account: Account = Account(new_account_key.account_id, new_account_key.account_type, "John Doe", 100000.00) 40 | 41 | print(f"Add new account {new_account} with key {new_account_key}") 42 | await named_map.put(new_account_key, new_account) 43 | 44 | print("NamedMap size is :", await named_map.size()) 45 | 46 | print("Account from get() :", await named_map.get(new_account_key)) 47 | 48 | print("Update account balance using processor ...") 49 | await named_map.invoke(new_account_key, Processors.update("balance", new_account.balance + 1000)) 50 | 51 | print("Updated account is :", await named_map.get(new_account_key)) 52 | 53 | await named_map.remove(new_account_key) 54 | 55 | print("NamedMap size is :", await named_map.size()) 56 | finally: 57 | await session.close() 58 | 59 | 60 | asyncio.run(do_run()) 61 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | [tool.poetry] 5 | name = "coherence-client" 6 | version = "2.0.2" 7 | description = """The Coherence Python Client allows Python applications to act as cache clients to a \ 8 | Coherence Cluster using Google's gRPC framework as the network transport.""" 9 | packages = [ 10 | { include = "coherence", from = "./src"}, 11 | ] 12 | readme = "README.md" 13 | authors = ["Oracle "] 14 | homepage = "https://github.com/oracle/coherence-py-client" 15 | repository = "https://github.com/oracle/coherence-py-client" 16 | classifiers = [ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: Universal Permissive License (UPL)", 19 | "Operating System :: OS Independent", 20 | "Development Status :: 5 - Production/Stable", 21 | ] 22 | 23 | [tool.poetry.dependencies] 24 | python = "^3.9" 25 | protobuf = "5.29.5" 26 | grpcio = "1.72.1" 27 | grpcio-tools = "1.71.0" 28 | jsonpickle = ">=3.0,<4.2" 29 | pymitter = ">=0.4,<1.1" 30 | typing-extensions = ">=4.11,<4.14" 31 | types-protobuf = "6.30.2.20250516" 32 | pympler = "1.1" 33 | numpy = "2.0.2" 34 | 35 | [tool.poetry.dev-dependencies] 36 | pytest = "~8.4" 37 | pytest-asyncio = "~0.26" 38 | pytest-cov = "~6.1" 39 | pytest-unordered = "~0.7" 40 | pre-commit = "~4.2" 41 | docutils="~0.20" 42 | Sphinx = "~7.4" 43 | sphinx-rtd-theme = "~3.0" 44 | sphinxcontrib-napoleon = "~0.7" 45 | m2r = "~0.3" 46 | third-party-license-file-generator = "~2024.8" 47 | pyinstrument="5.0.2" 48 | 49 | [tool.pytest.ini_options] 50 | asyncio_default_fixture_loop_scope = "function" 51 | pythonpath = ["src"] 52 | 53 | [tool.isort] 54 | multi_line_output = 3 55 | include_trailing_comma = true 56 | force_grid_wrap = 0 57 | use_parentheses = true 58 | line_length = 120 59 | 60 | [tool.black] 61 | line-length = 120 62 | target-version = ['py310'] 63 | include = '\.pyi?$' 64 | exclude = ''' 65 | 66 | ( 67 | /( 68 | \.eggs # exclude a few common directories in the 69 | | \.git # root of the project 70 | | \.hg 71 | | \.mypy_cache 72 | | \.tox 73 | | \.venv 74 | | _build 75 | | buck-out 76 | | build 77 | | dist 78 | )/ 79 | | foo.py # also separately exclude a file named foo.py in 80 | # the root of the project 81 | ) 82 | ''' 83 | 84 | 85 | [build-system] 86 | requires = ["poetry-core>=1.0.0"] 87 | build-backend = "poetry.core.masonry.api" 88 | -------------------------------------------------------------------------------- /sbom_generation.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. 2 | 3 | # This OCI DevOps build specification file [1] generates a Software Bill of Materials (SBOM) of the repository. 4 | # The file is needed to run checks for third-party vulnerabilities and business approval according to Oracle’s GitHub policies. 5 | # [1] https://docs.oracle.com/en-us/iaas/Content/devops/using/build_specs.htm 6 | 7 | version: 0.1 8 | component: build 9 | timeoutInSeconds: 1000 10 | shell: bash 11 | env: 12 | variables: 13 | PYTHON_CMD: "python3" 14 | CDXGEN_DEBUG_MODE: "debug" 15 | steps: 16 | - type: Command 17 | name: "Download the version 10.10.0 of cdxgen globally" 18 | command: | 19 | npm install -g @cyclonedx/cdxgen@10.10.0 20 | - type: Command 21 | name: "Workaround to let cdxgen run on nodejs 16" 22 | command: | 23 | # cdxgen relies on a fourth-party dependency that cannot be executed in a Node.js environment running version 16 24 | # (as installed on the build runner instance) 25 | # This is a workaround to ensure cdxgen functions correctly, even in an older Node.js environment. 26 | cd /node/node-v16.14.2-linux-x64/lib/node_modules/@cyclonedx/cdxgen && \ 27 | npm install cheerio@v1.0.0-rc.12 28 | - type: Command 29 | name: "Generate SBOM for Python " 30 | command: | 31 | # Search the test or dev requirements files, so that test and dev py packages can be excluded in the generated SBOM 32 | files=$(find . -type f -regex ".*\(test.*requirements\|requirements.*test\|dev.*requirements\|requirements.*dev\).*\.txt") && \ 33 | if [ -n "$files" ]; then \ 34 | cdxgen -t python -o artifactSBOM.json --spec-version 1.4 \ 35 | --exclude "*{requirements,dev,test}*{requirements,dev,test}*.txt" --project-name "$(basename $OCI_PRIMARY_SOURCE_URL)" --no-recurse 36 | else \ 37 | cdxgen -t python -o artifactSBOM.json --spec-version 1.4 --project-name "$(basename $OCI_PRIMARY_SOURCE_URL)" --no-recurse 38 | fi \ 39 | outputArtifacts: 40 | - name: artifactSBOM 41 | type: BINARY 42 | location: ${OCI_PRIMARY_SOURCE_DIR}/artifactSBOM.json 43 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | [flake8] 6 | per-file-ignores = __init__.py:F401 7 | max-line-length = 120 8 | extend-ignore = 9 | # See https://github.com/PyCQA/pycodestyle/issues/373 10 | E203, 11 | 12 | [mypy] 13 | follow_imports = silent 14 | strict_optional = True 15 | warn_redundant_casts = True 16 | warn_unused_ignores = True 17 | disallow_any_generics = True 18 | check_untyped_defs = True 19 | no_implicit_reexport = True 20 | disallow_untyped_defs = True 21 | ignore_missing_imports = True 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from distutils.core import setup 6 | 7 | setup( 8 | name="coherence-client", 9 | version="2.0.2", 10 | packages=["coherence"], 11 | url="https://github.com/oracle/coherence-py-client", 12 | license="UPL", 13 | author="Oracle Coherence Team", 14 | author_email="", 15 | description="Oracle Coherence client for Python", 16 | ) 17 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.python.version=3.11 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | -------------------------------------------------------------------------------- /src/coherence/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from __future__ import annotations 6 | 7 | __version__ = "2.0.2" 8 | 9 | import contextvars 10 | import logging 11 | from typing import Final 12 | 13 | # expose these symbols in top-level namespace 14 | from .aggregator import Aggregators as Aggregators 15 | from .client import CacheOptions as CacheOptions 16 | from .client import NamedCache as NamedCache 17 | from .client import NamedMap as NamedMap 18 | from .client import Options as Options 19 | from .client import Session as Session 20 | from .client import TlsOptions as TlsOptions 21 | from .client import request_timeout as request_timeout 22 | from .comparator import Comparator as Comparator 23 | from .entry import MapEntry as MapEntry 24 | from .extractor import Extractors as Extractors 25 | from .filter import Filters as Filters 26 | from .local_cache import CacheStats as CacheStats 27 | from .local_cache import NearCacheOptions as NearCacheOptions 28 | from .processor import Processors as Processors 29 | 30 | # default logging configuration for coherence 31 | handler: logging.StreamHandler = logging.StreamHandler() # type: ignore 32 | handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) 33 | 34 | _TIMEOUT_CONTEXT_VAR: Final[contextvars.ContextVar[float]] = contextvars.ContextVar("coherence-request-timeout") 35 | 36 | COH_LOG = logging.getLogger("coherence") 37 | COH_LOG.setLevel(logging.INFO) 38 | COH_LOG.addHandler(handler) 39 | -------------------------------------------------------------------------------- /src/coherence/cache_service_messages_v1_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: cache_service_messages_v1.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | import coherence.common_messages_v1_pb2 as common__messages__v1__pb2 15 | from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 16 | from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 17 | from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 18 | from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 19 | 20 | 21 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63\x61\x63he_service_messages_v1.proto\x12\x12\x63oherence.cache.v1\x1a\x18\x63ommon_messages_v1.proto\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa6\x01\n\x11NamedCacheRequest\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).coherence.cache.v1.NamedCacheRequestType\x12\x14\n\x07\x63\x61\x63heId\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12*\n\x07message\x18\x03 \x01(\x0b\x32\x14.google.protobuf.AnyH\x01\x88\x01\x01\x42\n\n\x08_cacheIdB\n\n\x08_message\"\x8d\x01\n\x12NamedCacheResponse\x12\x0f\n\x07\x63\x61\x63heId\x18\x01 \x01(\x05\x12.\n\x04type\x18\x02 \x01(\x0e\x32 .coherence.cache.v1.ResponseType\x12*\n\x07message\x18\x03 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x88\x01\x01\x42\n\n\x08_message\"#\n\x12\x45nsureCacheRequest\x12\r\n\x05\x63\x61\x63he\x18\x01 \x01(\t\"B\n\nPutRequest\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\x10\n\x03ttl\x18\x03 \x01(\x03H\x00\x88\x01\x01\x42\x06\n\x04_ttl\"b\n\rPutAllRequest\x12\x37\n\x07\x65ntries\x18\x01 \x03(\x0b\x32&.coherence.common.v1.BinaryKeyAndValue\x12\x10\n\x03ttl\x18\x02 \x01(\x03H\x00\x88\x01\x01\x42\x06\n\x04_ttl\"M\n\x15ReplaceMappingRequest\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x15\n\rpreviousValue\x18\x02 \x01(\x0c\x12\x10\n\x08newValue\x18\x03 \x01(\x0c\"v\n\x0cIndexRequest\x12\x0b\n\x03\x61\x64\x64\x18\x01 \x01(\x08\x12\x11\n\textractor\x18\x02 \x01(\x0c\x12\x13\n\x06sorted\x18\x03 \x01(\x08H\x00\x88\x01\x01\x12\x17\n\ncomparator\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x42\t\n\x07_sortedB\r\n\x0b_comparator\"|\n\x0cKeysOrFilter\x12\r\n\x03key\x18\x01 \x01(\x0cH\x00\x12<\n\x04keys\x18\x02 \x01(\x0b\x32,.coherence.common.v1.CollectionOfBytesValuesH\x00\x12\x10\n\x06\x66ilter\x18\x03 \x01(\x0cH\x00\x42\r\n\x0bkeyOrFilter\"=\n\x0bKeyOrFilter\x12\r\n\x03key\x18\x01 \x01(\x0cH\x00\x12\x10\n\x06\x66ilter\x18\x02 \x01(\x0cH\x00\x42\r\n\x0bkeyOrFilter\"]\n\x0e\x45xecuteRequest\x12\r\n\x05\x61gent\x18\x01 \x01(\x0c\x12\x33\n\x04keys\x18\x03 \x01(\x0b\x32 .coherence.cache.v1.KeysOrFilterH\x00\x88\x01\x01\x42\x07\n\x05_keys\"V\n\x0cQueryRequest\x12\x13\n\x06\x66ilter\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x17\n\ncomparator\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x42\t\n\x07_filterB\r\n\x0b_comparator\"\xc9\x01\n\x12MapListenerRequest\x12\x11\n\tsubscribe\x18\x01 \x01(\x08\x12\x39\n\x0bkeyOrFilter\x18\x02 \x01(\x0b\x32\x1f.coherence.cache.v1.KeyOrFilterH\x00\x88\x01\x01\x12\x10\n\x08\x66ilterId\x18\x03 \x01(\x03\x12\x0c\n\x04lite\x18\x04 \x01(\x08\x12\x13\n\x0bsynchronous\x18\x05 \x01(\x08\x12\x0f\n\x07priming\x18\x06 \x01(\x08\x12\x0f\n\x07trigger\x18\x07 \x01(\x0c\x42\x0e\n\x0c_keyOrFilter\"\xd5\x02\n\x0fMapEventMessage\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x10\n\x08newValue\x18\x03 \x01(\x0c\x12\x10\n\x08oldValue\x18\x04 \x01(\x0c\x12T\n\x13transformationState\x18\x05 \x01(\x0e\x32\x37.coherence.cache.v1.MapEventMessage.TransformationState\x12\x11\n\tfilterIds\x18\x06 \x03(\x03\x12\x11\n\tsynthetic\x18\x07 \x01(\x08\x12\x0f\n\x07priming\x18\x08 \x01(\x08\x12\x0f\n\x07\x65xpired\x18\t \x01(\x08\x12\x15\n\rversionUpdate\x18\n \x01(\x08\"P\n\x13TransformationState\x12\x15\n\x11NON_TRANSFORMABLE\x10\x00\x12\x11\n\rTRANSFORMABLE\x10\x01\x12\x0f\n\x0bTRANSFORMED\x10\x02*\xbd\x03\n\x15NamedCacheRequestType\x12\x0b\n\x07Unknown\x10\x00\x12\x0f\n\x0b\x45nsureCache\x10\x01\x12\r\n\tAggregate\x10\x02\x12\t\n\x05\x43lear\x10\x03\x12\x11\n\rContainsEntry\x10\x04\x12\x0f\n\x0b\x43ontainsKey\x10\x05\x12\x11\n\rContainsValue\x10\x06\x12\x0b\n\x07\x44\x65stroy\x10\x07\x12\x0b\n\x07IsEmpty\x10\x08\x12\x0b\n\x07IsReady\x10\t\x12\x07\n\x03Get\x10\n\x12\n\n\x06GetAll\x10\x0b\x12\t\n\x05Index\x10\x0c\x12\n\n\x06Invoke\x10\r\x12\x0f\n\x0bMapListener\x10\x0e\x12\x11\n\rPageOfEntries\x10\x0f\x12\x0e\n\nPageOfKeys\x10\x10\x12\x07\n\x03Put\x10\x11\x12\n\n\x06PutAll\x10\x12\x12\x0f\n\x0bPutIfAbsent\x10\x13\x12\x10\n\x0cQueryEntries\x10\x14\x12\r\n\tQueryKeys\x10\x15\x12\x0f\n\x0bQueryValues\x10\x16\x12\n\n\x06Remove\x10\x17\x12\x11\n\rRemoveMapping\x10\x18\x12\x0b\n\x07Replace\x10\x19\x12\x12\n\x0eReplaceMapping\x10\x1a\x12\x08\n\x04Size\x10\x1b\x12\x0c\n\x08Truncate\x10\x1c*G\n\x0cResponseType\x12\x0b\n\x07Message\x10\x00\x12\x0c\n\x08MapEvent\x10\x01\x12\r\n\tDestroyed\x10\x02\x12\r\n\tTruncated\x10\x03\x42/\n+com.oracle.coherence.grpc.messages.cache.v1P\x01\x62\x06proto3') 22 | 23 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 24 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cache_service_messages_v1_pb2', globals()) 25 | if _descriptor._USE_C_DESCRIPTORS == False: 26 | 27 | DESCRIPTOR._options = None 28 | DESCRIPTOR._serialized_options = b'\n+com.oracle.coherence.grpc.messages.cache.v1P\001' 29 | _NAMEDCACHEREQUESTTYPE._serialized_start=1840 30 | _NAMEDCACHEREQUESTTYPE._serialized_end=2285 31 | _RESPONSETYPE._serialized_start=2287 32 | _RESPONSETYPE._serialized_end=2358 33 | _NAMEDCACHEREQUEST._serialized_start=203 34 | _NAMEDCACHEREQUEST._serialized_end=369 35 | _NAMEDCACHERESPONSE._serialized_start=372 36 | _NAMEDCACHERESPONSE._serialized_end=513 37 | _ENSURECACHEREQUEST._serialized_start=515 38 | _ENSURECACHEREQUEST._serialized_end=550 39 | _PUTREQUEST._serialized_start=552 40 | _PUTREQUEST._serialized_end=618 41 | _PUTALLREQUEST._serialized_start=620 42 | _PUTALLREQUEST._serialized_end=718 43 | _REPLACEMAPPINGREQUEST._serialized_start=720 44 | _REPLACEMAPPINGREQUEST._serialized_end=797 45 | _INDEXREQUEST._serialized_start=799 46 | _INDEXREQUEST._serialized_end=917 47 | _KEYSORFILTER._serialized_start=919 48 | _KEYSORFILTER._serialized_end=1043 49 | _KEYORFILTER._serialized_start=1045 50 | _KEYORFILTER._serialized_end=1106 51 | _EXECUTEREQUEST._serialized_start=1108 52 | _EXECUTEREQUEST._serialized_end=1201 53 | _QUERYREQUEST._serialized_start=1203 54 | _QUERYREQUEST._serialized_end=1289 55 | _MAPLISTENERREQUEST._serialized_start=1292 56 | _MAPLISTENERREQUEST._serialized_end=1493 57 | _MAPEVENTMESSAGE._serialized_start=1496 58 | _MAPEVENTMESSAGE._serialized_end=1837 59 | _MAPEVENTMESSAGE_TRANSFORMATIONSTATE._serialized_start=1757 60 | _MAPEVENTMESSAGE_TRANSFORMATIONSTATE._serialized_end=1837 61 | # @@protoc_insertion_point(module_scope) 62 | -------------------------------------------------------------------------------- /src/coherence/cache_service_messages_v1_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /src/coherence/common_messages_v1_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: common_messages_v1.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x63ommon_messages_v1.proto\x12\x13\x63oherence.common.v1\x1a\x19google/protobuf/any.proto\"=\n\x0c\x45rrorMessage\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x12\n\x05\x65rror\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x42\x08\n\x06_error\"\n\n\x08\x43omplete\";\n\x10HeartbeatMessage\x12\x11\n\x04uuid\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03\x61\x63k\x18\x02 \x01(\x08\x42\x07\n\x05_uuid\"/\n\rOptionalValue\x12\x0f\n\x07present\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c\")\n\x17\x43ollectionOfBytesValues\x12\x0e\n\x06values\x18\x01 \x03(\x0c\"/\n\x11\x42inaryKeyAndValue\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05value\x18\x02 \x01(\x0c\x42\x30\n,com.oracle.coherence.grpc.messages.common.v1P\x01\x62\x06proto3') 18 | 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'common_messages_v1_pb2', globals()) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | 23 | DESCRIPTOR._options = None 24 | DESCRIPTOR._serialized_options = b'\n,com.oracle.coherence.grpc.messages.common.v1P\001' 25 | _ERRORMESSAGE._serialized_start=76 26 | _ERRORMESSAGE._serialized_end=137 27 | _COMPLETE._serialized_start=139 28 | _COMPLETE._serialized_end=149 29 | _HEARTBEATMESSAGE._serialized_start=151 30 | _HEARTBEATMESSAGE._serialized_end=210 31 | _OPTIONALVALUE._serialized_start=212 32 | _OPTIONALVALUE._serialized_end=259 33 | _COLLECTIONOFBYTESVALUES._serialized_start=261 34 | _COLLECTIONOFBYTESVALUES._serialized_end=302 35 | _BINARYKEYANDVALUE._serialized_start=304 36 | _BINARYKEYANDVALUE._serialized_end=351 37 | # @@protoc_insertion_point(module_scope) 38 | -------------------------------------------------------------------------------- /src/coherence/common_messages_v1_pb2.pyi: -------------------------------------------------------------------------------- 1 | # mypy: ignore-errors 2 | from google.protobuf import any_pb2 as _any_pb2 3 | from google.protobuf.internal import containers as _containers 4 | from google.protobuf import descriptor as _descriptor 5 | from google.protobuf import message as _message 6 | from typing import ClassVar as _ClassVar, Iterable as _Iterable, Optional as _Optional 7 | 8 | DESCRIPTOR: _descriptor.FileDescriptor 9 | 10 | class BinaryKeyAndValue(_message.Message): 11 | __slots__ = ["key", "value"] 12 | KEY_FIELD_NUMBER: _ClassVar[int] 13 | VALUE_FIELD_NUMBER: _ClassVar[int] 14 | key: bytes 15 | value: bytes 16 | def __init__(self, key: _Optional[bytes] = ..., value: _Optional[bytes] = ...) -> None: ... 17 | 18 | class CollectionOfBytesValues(_message.Message): 19 | __slots__ = ["values"] 20 | VALUES_FIELD_NUMBER: _ClassVar[int] 21 | values: _containers.RepeatedScalarFieldContainer[bytes] 22 | def __init__(self, values: _Optional[_Iterable[bytes]] = ...) -> None: ... 23 | 24 | class Complete(_message.Message): 25 | __slots__ = [] 26 | def __init__(self) -> None: ... 27 | 28 | class ErrorMessage(_message.Message): 29 | __slots__ = ["error", "message"] 30 | ERROR_FIELD_NUMBER: _ClassVar[int] 31 | MESSAGE_FIELD_NUMBER: _ClassVar[int] 32 | error: bytes 33 | message: str 34 | def __init__(self, message: _Optional[str] = ..., error: _Optional[bytes] = ...) -> None: ... 35 | 36 | class HeartbeatMessage(_message.Message): 37 | __slots__ = ["ack", "uuid"] 38 | ACK_FIELD_NUMBER: _ClassVar[int] 39 | UUID_FIELD_NUMBER: _ClassVar[int] 40 | ack: bool 41 | uuid: bytes 42 | def __init__(self, uuid: _Optional[bytes] = ..., ack: bool = ...) -> None: ... 43 | 44 | class OptionalValue(_message.Message): 45 | __slots__ = ["present", "value"] 46 | PRESENT_FIELD_NUMBER: _ClassVar[int] 47 | VALUE_FIELD_NUMBER: _ClassVar[int] 48 | present: bool 49 | value: bytes 50 | def __init__(self, present: bool = ..., value: _Optional[bytes] = ...) -> None: ... 51 | -------------------------------------------------------------------------------- /src/coherence/common_messages_v1_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /src/coherence/comparator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from __future__ import annotations 6 | 7 | from abc import ABC 8 | from typing import Any 9 | 10 | from .extractor import UniversalExtractor 11 | from .serialization import proxy 12 | 13 | 14 | class Comparator(ABC): 15 | """Comparator is used to control the ordering for collections of objects""" 16 | 17 | def __init__(self) -> None: 18 | super().__init__() 19 | 20 | 21 | @proxy("comparator.SafeComparator") 22 | class SafeComparator(Comparator): 23 | """None-safe delegating comparator. None values are evaluated as "less then" any non None value.""" 24 | 25 | def __init__(self, property_name: str) -> None: 26 | super().__init__() 27 | self.comparator = ExtractorComparator(property_name) 28 | 29 | 30 | @proxy("comparator.InverseComparator") 31 | class InverseComparator(Comparator): 32 | """Comparator that reverses the result of another comparator.""" 33 | 34 | def __init__(self, property_name: str) -> None: 35 | super().__init__() 36 | self.comparator = ExtractorComparator(property_name) 37 | 38 | 39 | @proxy("comparator.ExtractorComparator") 40 | class ExtractorComparator(Comparator): 41 | """Comparator implementation that uses specified :class:`coherence.extractor.ValueExtractor` to 42 | extract value(s) to be used for comparison.""" 43 | 44 | def __init__(self, property_name: str) -> None: 45 | super().__init__() 46 | self.extractor = UniversalExtractor[Any](property_name) 47 | -------------------------------------------------------------------------------- /src/coherence/entry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from __future__ import annotations 6 | 7 | from typing import Generic, TypeVar 8 | 9 | K = TypeVar("K") 10 | V = TypeVar("V") 11 | 12 | 13 | class MapEntry(Generic[K, V]): 14 | """ 15 | A map entry (key-value pair). 16 | """ 17 | 18 | def __init__(self, key: K, value: V): 19 | self._key = key 20 | self._value = value 21 | 22 | @property 23 | def key(self) -> K: 24 | return self._key 25 | 26 | @property 27 | def value(self) -> V: 28 | return self._value 29 | 30 | def __str__(self) -> str: 31 | return f"MapEntry(key={self.key}, value={self.value})" 32 | -------------------------------------------------------------------------------- /src/coherence/messages_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_messages_v1_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: proxy_service_messages_v1.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | import coherence.common_messages_v1_pb2 as common__messages__v1__pb2 15 | from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 16 | 17 | 18 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fproxy_service_messages_v1.proto\x12\x12\x63oherence.proxy.v1\x1a\x18\x63ommon_messages_v1.proto\x1a\x19google/protobuf/any.proto\"\xbb\x01\n\x0cProxyRequest\x12\n\n\x02id\x18\x01 \x01(\x03\x12/\n\x04init\x18\x03 \x01(\x0b\x32\x1f.coherence.proxy.v1.InitRequestH\x00\x12\'\n\x07message\x18\x04 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x12:\n\theartbeat\x18\x05 \x01(\x0b\x32%.coherence.common.v1.HeartbeatMessageH\x00\x42\t\n\x07request\"\xa5\x02\n\rProxyResponse\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x30\n\x04init\x18\x04 \x01(\x0b\x32 .coherence.proxy.v1.InitResponseH\x00\x12\'\n\x07message\x18\x05 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x12\x32\n\x05\x65rror\x18\x06 \x01(\x0b\x32!.coherence.common.v1.ErrorMessageH\x00\x12\x31\n\x08\x63omplete\x18\x07 \x01(\x0b\x32\x1d.coherence.common.v1.CompleteH\x00\x12:\n\theartbeat\x18\x08 \x01(\x0b\x32%.coherence.common.v1.HeartbeatMessageH\x00\x42\n\n\x08response\"\xc7\x01\n\x0bInitRequest\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x03 \x01(\t\x12\x10\n\x08protocol\x18\x04 \x01(\t\x12\x17\n\x0fprotocolVersion\x18\x05 \x01(\x05\x12 \n\x18supportedProtocolVersion\x18\x06 \x01(\x05\x12\x16\n\theartbeat\x18\x07 \x01(\x03H\x00\x88\x01\x01\x12\x17\n\nclientUuid\x18\x08 \x01(\x0cH\x01\x88\x01\x01\x42\x0c\n\n_heartbeatB\r\n\x0b_clientUuid\"\x8e\x01\n\x0cInitResponse\x12\x0c\n\x04uuid\x18\x01 \x01(\x0c\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x16\n\x0e\x65ncodedVersion\x18\x03 \x01(\x05\x12\x17\n\x0fprotocolVersion\x18\x04 \x01(\x05\x12\x15\n\rproxyMemberId\x18\x05 \x01(\x05\x12\x17\n\x0fproxyMemberUuid\x18\x06 \x01(\x0c\x42/\n+com.oracle.coherence.grpc.messages.proxy.v1P\x01\x62\x06proto3') 19 | 20 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 21 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proxy_service_messages_v1_pb2', globals()) 22 | if _descriptor._USE_C_DESCRIPTORS == False: 23 | 24 | DESCRIPTOR._options = None 25 | DESCRIPTOR._serialized_options = b'\n+com.oracle.coherence.grpc.messages.proxy.v1P\001' 26 | _PROXYREQUEST._serialized_start=109 27 | _PROXYREQUEST._serialized_end=296 28 | _PROXYRESPONSE._serialized_start=299 29 | _PROXYRESPONSE._serialized_end=592 30 | _INITREQUEST._serialized_start=595 31 | _INITREQUEST._serialized_end=794 32 | _INITRESPONSE._serialized_start=797 33 | _INITRESPONSE._serialized_end=939 34 | # @@protoc_insertion_point(module_scope) 35 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_messages_v1_pb2.pyi: -------------------------------------------------------------------------------- 1 | # mypy: ignore-errors 2 | import common_messages_v1_pb2 as _common_messages_v1_pb2 3 | from google.protobuf import any_pb2 as _any_pb2 4 | from google.protobuf import descriptor as _descriptor 5 | from google.protobuf import message as _message 6 | from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union 7 | 8 | DESCRIPTOR: _descriptor.FileDescriptor 9 | 10 | class InitRequest(_message.Message): 11 | __slots__ = ["clientUuid", "format", "heartbeat", "protocol", "protocolVersion", "scope", "supportedProtocolVersion"] 12 | CLIENTUUID_FIELD_NUMBER: _ClassVar[int] 13 | FORMAT_FIELD_NUMBER: _ClassVar[int] 14 | HEARTBEAT_FIELD_NUMBER: _ClassVar[int] 15 | PROTOCOLVERSION_FIELD_NUMBER: _ClassVar[int] 16 | PROTOCOL_FIELD_NUMBER: _ClassVar[int] 17 | SCOPE_FIELD_NUMBER: _ClassVar[int] 18 | SUPPORTEDPROTOCOLVERSION_FIELD_NUMBER: _ClassVar[int] 19 | clientUuid: bytes 20 | format: str 21 | heartbeat: int 22 | protocol: str 23 | protocolVersion: int 24 | scope: str 25 | supportedProtocolVersion: int 26 | def __init__(self, scope: _Optional[str] = ..., format: _Optional[str] = ..., protocol: _Optional[str] = ..., protocolVersion: _Optional[int] = ..., supportedProtocolVersion: _Optional[int] = ..., heartbeat: _Optional[int] = ..., clientUuid: _Optional[bytes] = ...) -> None: ... 27 | 28 | class InitResponse(_message.Message): 29 | __slots__ = ["encodedVersion", "protocolVersion", "proxyMemberId", "proxyMemberUuid", "uuid", "version"] 30 | ENCODEDVERSION_FIELD_NUMBER: _ClassVar[int] 31 | PROTOCOLVERSION_FIELD_NUMBER: _ClassVar[int] 32 | PROXYMEMBERID_FIELD_NUMBER: _ClassVar[int] 33 | PROXYMEMBERUUID_FIELD_NUMBER: _ClassVar[int] 34 | UUID_FIELD_NUMBER: _ClassVar[int] 35 | VERSION_FIELD_NUMBER: _ClassVar[int] 36 | encodedVersion: int 37 | protocolVersion: int 38 | proxyMemberId: int 39 | proxyMemberUuid: bytes 40 | uuid: bytes 41 | version: str 42 | def __init__(self, uuid: _Optional[bytes] = ..., version: _Optional[str] = ..., encodedVersion: _Optional[int] = ..., protocolVersion: _Optional[int] = ..., proxyMemberId: _Optional[int] = ..., proxyMemberUuid: _Optional[bytes] = ...) -> None: ... 43 | 44 | class ProxyRequest(_message.Message): 45 | __slots__ = ["heartbeat", "id", "init", "message"] 46 | HEARTBEAT_FIELD_NUMBER: _ClassVar[int] 47 | ID_FIELD_NUMBER: _ClassVar[int] 48 | INIT_FIELD_NUMBER: _ClassVar[int] 49 | MESSAGE_FIELD_NUMBER: _ClassVar[int] 50 | heartbeat: _common_messages_v1_pb2.HeartbeatMessage 51 | id: int 52 | init: InitRequest 53 | message: _any_pb2.Any 54 | def __init__(self, id: _Optional[int] = ..., init: _Optional[_Union[InitRequest, _Mapping]] = ..., message: _Optional[_Union[_any_pb2.Any, _Mapping]] = ..., heartbeat: _Optional[_Union[_common_messages_v1_pb2.HeartbeatMessage, _Mapping]] = ...) -> None: ... 55 | 56 | class ProxyResponse(_message.Message): 57 | __slots__ = ["complete", "error", "heartbeat", "id", "init", "message"] 58 | COMPLETE_FIELD_NUMBER: _ClassVar[int] 59 | ERROR_FIELD_NUMBER: _ClassVar[int] 60 | HEARTBEAT_FIELD_NUMBER: _ClassVar[int] 61 | ID_FIELD_NUMBER: _ClassVar[int] 62 | INIT_FIELD_NUMBER: _ClassVar[int] 63 | MESSAGE_FIELD_NUMBER: _ClassVar[int] 64 | complete: _common_messages_v1_pb2.Complete 65 | error: _common_messages_v1_pb2.ErrorMessage 66 | heartbeat: _common_messages_v1_pb2.HeartbeatMessage 67 | id: int 68 | init: InitResponse 69 | message: _any_pb2.Any 70 | def __init__(self, id: _Optional[int] = ..., init: _Optional[_Union[InitResponse, _Mapping]] = ..., message: _Optional[_Union[_any_pb2.Any, _Mapping]] = ..., error: _Optional[_Union[_common_messages_v1_pb2.ErrorMessage, _Mapping]] = ..., complete: _Optional[_Union[_common_messages_v1_pb2.Complete, _Mapping]] = ..., heartbeat: _Optional[_Union[_common_messages_v1_pb2.HeartbeatMessage, _Mapping]] = ...) -> None: ... 71 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_messages_v1_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_v1_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: proxy_service_v1.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | import coherence.proxy_service_messages_v1_pb2 as proxy__service__messages__v1__pb2 15 | from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 16 | from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 17 | 18 | 19 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16proxy_service_v1.proto\x12\x12\x63oherence.proxy.v1\x1a\x1fproxy_service_messages_v1.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1egoogle/protobuf/wrappers.proto2g\n\x0cProxyService\x12W\n\nsubChannel\x12 .coherence.proxy.v1.ProxyRequest\x1a!.coherence.proxy.v1.ProxyResponse\"\x00(\x01\x30\x01\x42/\n+com.oracle.coherence.grpc.services.proxy.v1P\x01\x62\x06proto3') 20 | 21 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 22 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proxy_service_v1_pb2', globals()) 23 | if _descriptor._USE_C_DESCRIPTORS == False: 24 | 25 | DESCRIPTOR._options = None 26 | DESCRIPTOR._serialized_options = b'\n+com.oracle.coherence.grpc.services.proxy.v1P\001' 27 | _PROXYSERVICE._serialized_start=140 28 | _PROXYSERVICE._serialized_end=243 29 | # @@protoc_insertion_point(module_scope) 30 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_v1_pb2.pyi: -------------------------------------------------------------------------------- 1 | # mypy: ignore-errors 2 | import proxy_service_messages_v1_pb2 as _proxy_service_messages_v1_pb2 3 | from google.protobuf import empty_pb2 as _empty_pb2 4 | from google.protobuf import wrappers_pb2 as _wrappers_pb2 5 | from google.protobuf import descriptor as _descriptor 6 | from typing import ClassVar as _ClassVar 7 | 8 | DESCRIPTOR: _descriptor.FileDescriptor 9 | -------------------------------------------------------------------------------- /src/coherence/proxy_service_v1_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import coherence.proxy_service_messages_v1_pb2 as proxy__service__messages__v1__pb2 6 | 7 | 8 | class ProxyServiceStub(object): 9 | """----------------------------------------------------------------- 10 | The Coherence gRPC Proxy Service definition. 11 | ----------------------------------------------------------------- 12 | 13 | """ 14 | 15 | def __init__(self, channel): 16 | """Constructor. 17 | 18 | Args: 19 | channel: A grpc.Channel. 20 | """ 21 | self.subChannel = channel.stream_stream( 22 | '/coherence.proxy.v1.ProxyService/subChannel', 23 | request_serializer=proxy__service__messages__v1__pb2.ProxyRequest.SerializeToString, 24 | response_deserializer=proxy__service__messages__v1__pb2.ProxyResponse.FromString, 25 | ) 26 | 27 | 28 | class ProxyServiceServicer(object): 29 | """----------------------------------------------------------------- 30 | The Coherence gRPC Proxy Service definition. 31 | ----------------------------------------------------------------- 32 | 33 | """ 34 | 35 | def subChannel(self, request_iterator, context): 36 | """Sets up a bidirectional channel for cache requests and responses. 37 | """ 38 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 39 | context.set_details('Method not implemented!') 40 | raise NotImplementedError('Method not implemented!') 41 | 42 | 43 | def add_ProxyServiceServicer_to_server(servicer, server): 44 | rpc_method_handlers = { 45 | 'subChannel': grpc.stream_stream_rpc_method_handler( 46 | servicer.subChannel, 47 | request_deserializer=proxy__service__messages__v1__pb2.ProxyRequest.FromString, 48 | response_serializer=proxy__service__messages__v1__pb2.ProxyResponse.SerializeToString, 49 | ), 50 | } 51 | generic_handler = grpc.method_handlers_generic_handler( 52 | 'coherence.proxy.v1.ProxyService', rpc_method_handlers) 53 | server.add_generic_rpc_handlers((generic_handler,)) 54 | 55 | 56 | # This class is part of an EXPERIMENTAL API. 57 | class ProxyService(object): 58 | """----------------------------------------------------------------- 59 | The Coherence gRPC Proxy Service definition. 60 | ----------------------------------------------------------------- 61 | 62 | """ 63 | 64 | @staticmethod 65 | def subChannel(request_iterator, 66 | target, 67 | options=(), 68 | channel_credentials=None, 69 | call_credentials=None, 70 | insecure=False, 71 | compression=None, 72 | wait_for_ready=None, 73 | timeout=None, 74 | metadata=None): 75 | return grpc.experimental.stream_stream(request_iterator, target, '/coherence.proxy.v1.ProxyService/subChannel', 76 | proxy__service__messages__v1__pb2.ProxyRequest.SerializeToString, 77 | proxy__service__messages__v1__pb2.ProxyResponse.FromString, 78 | options, channel_credentials, 79 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 80 | -------------------------------------------------------------------------------- /src/coherence/services_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: services.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | import messages_pb2 as messages__pb2 15 | from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 16 | from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 17 | 18 | 19 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eservices.proto\x12\tcoherence\x1a\x0emessages.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1egoogle/protobuf/wrappers.proto2\xea\x0f\n\x11NamedCacheService\x12@\n\x08\x61\x64\x64Index\x12\x1a.coherence.AddIndexRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n\taggregate\x12\x1b.coherence.AggregateRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12:\n\x05\x63lear\x12\x17.coherence.ClearRequest\x1a\x16.google.protobuf.Empty\"\x00\x12N\n\rcontainsEntry\x12\x1f.coherence.ContainsEntryRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12J\n\x0b\x63ontainsKey\x12\x1d.coherence.ContainsKeyRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12N\n\rcontainsValue\x12\x1f.coherence.ContainsValueRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12>\n\x07\x64\x65stroy\x12\x19.coherence.DestroyRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x08\x65ntrySet\x12\x1a.coherence.EntrySetRequest\x1a\x10.coherence.Entry\"\x00\x30\x01\x12M\n\x06\x65vents\x12\x1d.coherence.MapListenerRequest\x1a\x1e.coherence.MapListenerResponse\"\x00(\x01\x30\x01\x12\x38\n\x03get\x12\x15.coherence.GetRequest\x1a\x18.coherence.OptionalValue\"\x00\x12\x38\n\x06getAll\x12\x18.coherence.GetAllRequest\x1a\x10.coherence.Entry\"\x00\x30\x01\x12\x41\n\x06invoke\x12\x18.coherence.InvokeRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12>\n\tinvokeAll\x12\x1b.coherence.InvokeAllRequest\x1a\x10.coherence.Entry\"\x00\x30\x01\x12\x42\n\x07isEmpty\x12\x19.coherence.IsEmptyRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12\x42\n\x07isReady\x12\x19.coherence.IsReadyRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12\x43\n\x06keySet\x12\x18.coherence.KeySetRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x30\x01\x12\x46\n\x10nextEntrySetPage\x12\x16.coherence.PageRequest\x1a\x16.coherence.EntryResult\"\x00\x30\x01\x12I\n\x0enextKeySetPage\x12\x16.coherence.PageRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x30\x01\x12;\n\x03put\x12\x15.coherence.PutRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12<\n\x06putAll\x12\x18.coherence.PutAllRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n\x0bputIfAbsent\x12\x1d.coherence.PutIfAbsentRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12\x41\n\x06remove\x12\x18.coherence.RemoveRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12\x46\n\x0bremoveIndex\x12\x1d.coherence.RemoveIndexRequest\x1a\x16.google.protobuf.Empty\"\x00\x12N\n\rremoveMapping\x12\x1f.coherence.RemoveMappingRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12\x43\n\x07replace\x12\x19.coherence.ReplaceRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x12P\n\x0ereplaceMapping\x12 .coherence.ReplaceMappingRequest\x1a\x1a.google.protobuf.BoolValue\"\x00\x12=\n\x04size\x12\x16.coherence.SizeRequest\x1a\x1b.google.protobuf.Int32Value\"\x00\x12@\n\x08truncate\x12\x1a.coherence.TruncateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x06values\x12\x18.coherence.ValuesRequest\x1a\x1b.google.protobuf.BytesValue\"\x00\x30\x01\x42\x1d\n\x19\x63om.oracle.coherence.grpcP\x01\x62\x06proto3') 20 | 21 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 22 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'services_pb2', globals()) 23 | if _descriptor._USE_C_DESCRIPTORS == False: 24 | 25 | DESCRIPTOR._options = None 26 | DESCRIPTOR._serialized_options = b'\n\031com.oracle.coherence.grpcP\001' 27 | _NAMEDCACHESERVICE._serialized_start=107 28 | _NAMEDCACHESERVICE._serialized_end=2133 29 | # @@protoc_insertion_point(module_scope) 30 | -------------------------------------------------------------------------------- /src/coherence/services_pb2.pyi: -------------------------------------------------------------------------------- 1 | # mypy: ignore-errors 2 | import messages_pb2 as _messages_pb2 3 | from google.protobuf import empty_pb2 as _empty_pb2 4 | from google.protobuf import wrappers_pb2 as _wrappers_pb2 5 | from google.protobuf import descriptor as _descriptor 6 | from typing import ClassVar as _ClassVar 7 | 8 | DESCRIPTOR: _descriptor.FileDescriptor -------------------------------------------------------------------------------- /tests/address.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from __future__ import annotations 6 | 7 | 8 | class Address: 9 | def __init__(self, street: str, city: str, state: str, zipcode: int, country: str): 10 | self.street = street 11 | self.city = city 12 | self.state = state 13 | self.zipcode = zipcode 14 | self.country = country 15 | 16 | @classmethod 17 | def address(cls, street: str, city: str, state: str, zipcode: int, country: str) -> Address: 18 | return Address(street, city, state, zipcode, country) 19 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | import asyncio 5 | import time 6 | from typing import Any, AsyncGenerator 7 | 8 | import pytest_asyncio 9 | 10 | import tests 11 | from coherence import NamedCache, Session 12 | from tests.person import Person 13 | 14 | 15 | @pytest_asyncio.fixture 16 | async def test_session() -> AsyncGenerator[Session, None]: 17 | session: Session = await tests.get_session() 18 | yield session 19 | await session.close() 20 | await asyncio.sleep(0) # helps avoid loop already closed errors 21 | 22 | 23 | @pytest_asyncio.fixture 24 | async def cache(test_session: Session) -> AsyncGenerator[NamedCache[Any, Any], None]: 25 | cache: NamedCache[Any, Any] = await test_session.get_cache("test-" + str(time.time_ns())) 26 | yield cache 27 | await cache.truncate() 28 | 29 | 30 | @pytest_asyncio.fixture 31 | async def person_cache(cache: NamedCache[Any, Any]) -> AsyncGenerator[NamedCache[str, Person], None]: 32 | await Person.populate_named_map(cache) 33 | yield cache 34 | -------------------------------------------------------------------------------- /tests/e2e/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | -------------------------------------------------------------------------------- /tests/e2e/test_ai.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | import random 5 | import time 6 | from typing import List, Optional, cast 7 | 8 | import pytest 9 | 10 | from coherence import COH_LOG, Extractors, NamedCache, Session 11 | from coherence.ai import BinaryQuantIndex, DocumentChunk, FloatVector, HnswIndex, SimilaritySearch, Vectors 12 | 13 | 14 | class ValueWithVector: 15 | def __init__(self, vector: FloatVector, text: str, number: int) -> None: 16 | self.vector = vector 17 | self.text = text 18 | self.number = number 19 | 20 | def get_vector(self) -> FloatVector: 21 | return self.vector 22 | 23 | def get_text(self) -> str: 24 | return self.text 25 | 26 | def get_number(self) -> int: 27 | return self.number 28 | 29 | def __repr__(self) -> str: 30 | return f"ValueWithVector(vector={self.vector}, text='{self.text}', number={self.number})" 31 | 32 | 33 | def random_floats(n: int) -> List[float]: 34 | floats: List[float] = [0.0] * n 35 | for i in range(n): 36 | floats[i] = random.uniform(-50.0, 50.0) 37 | return floats 38 | 39 | 40 | DIMENSIONS: int = 384 41 | 42 | 43 | async def populate_vectors(vectors: NamedCache[int, ValueWithVector]) -> ValueWithVector: 44 | matches: List[List[float]] = [[]] * 5 45 | matches[0] = random_floats(DIMENSIONS) 46 | 47 | # Creating copies of matches[0] for matches[1] to matches[4] 48 | for i in range(1, 5): 49 | matches[i] = matches[0].copy() 50 | matches[i][0] += 1.0 # Modify the first element 51 | 52 | count = 10000 53 | values: List[Optional[ValueWithVector]] = [None] * count 54 | 55 | # Assign normalized vectors to the first 5 entries 56 | for i in range(5): 57 | values[i] = ValueWithVector(FloatVector(Vectors.normalize(matches[i])), str(i), i) 58 | await vectors.put(i, values[i]) 59 | 60 | # Fill the remaining values with random vectors 61 | for i in range(5, count): 62 | values[i] = ValueWithVector(FloatVector(Vectors.normalize(random_floats(DIMENSIONS))), str(i), i) 63 | await vectors.put(i, values[i]) 64 | 65 | return cast(ValueWithVector, values[0]) 66 | 67 | 68 | async def populate_document_chunk_vectors(vectors: NamedCache[int, DocumentChunk]) -> DocumentChunk: 69 | matches: List[List[float]] = [[]] * 5 70 | matches[0] = random_floats(DIMENSIONS) 71 | 72 | # Creating copies of matches[0] for matches[1] to matches[4] 73 | for i in range(1, 5): 74 | matches[i] = matches[0].copy() 75 | matches[i][0] += 1.0 # Modify the first element 76 | 77 | count = 10000 78 | values: List[Optional[DocumentChunk]] = [None] * count 79 | 80 | # Assign normalized vectors to the first 5 entries 81 | for i in range(5): 82 | values[i] = DocumentChunk(str(i), metadata=None, vector=FloatVector(Vectors.normalize(matches[i]))) 83 | await vectors.put(i, values[i]) 84 | 85 | # Fill the remaining values with random vectors 86 | for i in range(5, count): 87 | values[i] = DocumentChunk( 88 | str(i), metadata=None, vector=FloatVector(Vectors.normalize(random_floats(DIMENSIONS))) 89 | ) 90 | await vectors.put(i, values[i]) 91 | 92 | return cast(DocumentChunk, values[0]) 93 | 94 | 95 | @pytest.mark.asyncio 96 | @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") 97 | async def test_similarity_search_with_binary_quant_index(test_session: Session) -> None: 98 | await _run_similarity_search_with_index(test_session, "BinaryQuantIndex") 99 | 100 | 101 | @pytest.mark.asyncio 102 | @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") 103 | async def test_similarity_search_with_document_chunk(test_session: Session) -> None: 104 | cache: NamedCache[int, DocumentChunk] = await test_session.get_cache("vector_cache") 105 | dc: DocumentChunk = await populate_document_chunk_vectors(cache) 106 | 107 | # Create a SimilaritySearch aggregator 108 | value_extractor = Extractors.extract("vector") 109 | k = 10 110 | ss = SimilaritySearch(value_extractor, dc.vector, k) 111 | 112 | hnsw_result = await cache.aggregate(ss) 113 | 114 | assert hnsw_result is not None 115 | assert len(hnsw_result) == k 116 | COH_LOG.info("Results below for test_SimilaritySearch_with_DocumentChunk:") 117 | for e in hnsw_result: 118 | COH_LOG.info(e) 119 | 120 | await cache.truncate() 121 | await cache.destroy() 122 | 123 | 124 | @pytest.mark.asyncio 125 | @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") 126 | async def test_similarity_search_with_hnsw_index(test_session: Session) -> None: 127 | await _run_similarity_search_with_index(test_session, "HnswIndex") 128 | 129 | 130 | async def _run_similarity_search_with_index(test_session: Session, index_type: str) -> None: 131 | cache: NamedCache[int, ValueWithVector] = await test_session.get_cache("vector_cache") 132 | if index_type == "BinaryQuantIndex": 133 | cache.add_index(BinaryQuantIndex(Extractors.extract("vector"))) 134 | elif index_type == "HnswIndex": 135 | cache.add_index(HnswIndex(Extractors.extract("vector"), DIMENSIONS)) 136 | else: 137 | COH_LOG.error("NO index_type specified") 138 | return 139 | 140 | value_with_vector = await populate_vectors(cache) 141 | 142 | # Create a SimilaritySearch aggregator 143 | value_extractor = Extractors.extract("vector") 144 | k = 10 145 | ss = SimilaritySearch(value_extractor, value_with_vector.vector, k) 146 | 147 | ss.bruteForce = True # Set bruteForce to True 148 | start_time_bf = time.perf_counter() 149 | hnsw_result = await cache.aggregate(ss) 150 | end_time_bf = time.perf_counter() 151 | elapsed_time = end_time_bf - start_time_bf 152 | COH_LOG.info("Results below for test_SimilaritySearch with BruteForce true:") 153 | for e in hnsw_result: 154 | COH_LOG.info(e) 155 | COH_LOG.info(f"Elapsed time for brute force: {elapsed_time} seconds") 156 | 157 | assert hnsw_result is not None 158 | assert len(hnsw_result) == k 159 | 160 | ss.bruteForce = False 161 | start_time = time.perf_counter() 162 | hnsw_result = await cache.aggregate(ss) 163 | end_time = time.perf_counter() 164 | elapsed_time = end_time - start_time 165 | COH_LOG.info("Results below for test_SimilaritySearch with " + index_type + ":") 166 | for e in hnsw_result: 167 | COH_LOG.info(e) 168 | COH_LOG.info(f"Elapsed time: {elapsed_time} seconds") 169 | 170 | assert hnsw_result is not None 171 | assert len(hnsw_result) == k 172 | 173 | await cache.truncate() 174 | await cache.destroy() 175 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-jakarta/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 4.0.0 14 | 15 | 16 | com.oracle.coherence.python 17 | coherence-python-parent 18 | 1.0.0 19 | ../pom.xml 20 | 21 | 22 | coherence-python-client-data-jakarta 23 | 24 | Oracle Coherence Python Client Data Jakarta 25 | coherence-python-client-data-jakarta 26 | 27 | 28 | 29 | ${coherence.group.id} 30 | coherence-json 31 | ${coherence.version} 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-jakarta/src/main/java/com/oracle/coherence/python/testing/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | 8 | package com.oracle.coherence.python.testing; 9 | 10 | import java.io.Serializable; 11 | import java.util.Objects; 12 | import jakarta.json.bind.annotation.JsonbProperty; 13 | 14 | /** 15 | * Class to represent an Australian address. 16 | * 17 | * @author Tim Middleton 2022-12-22 18 | */ 19 | public class Address 20 | implements Serializable { 21 | 22 | @JsonbProperty("addressLine1") 23 | private String addressLine1; 24 | 25 | @JsonbProperty("addressLine2") 26 | private String addressLine2; 27 | 28 | @JsonbProperty("suburb") 29 | private String suburb; 30 | 31 | @JsonbProperty("city") 32 | private String city; 33 | 34 | @JsonbProperty("state") 35 | private String state; 36 | 37 | @JsonbProperty("postcode") 38 | private int postCode; 39 | 40 | public Address() { 41 | } 42 | 43 | public Address(String addressLine1, String addressLine2, String suburb, String city, String state, int postCode) { 44 | this.addressLine1 = addressLine1; 45 | this.addressLine2 = addressLine2; 46 | this.suburb = suburb; 47 | this.city = city; 48 | this.state = state; 49 | this.postCode = postCode; 50 | } 51 | 52 | public String getAddressLine1() { 53 | return addressLine1; 54 | } 55 | 56 | public void setAddressLine1(String addressLine1) { 57 | this.addressLine1 = addressLine1; 58 | } 59 | 60 | public String getAddressLine2() { 61 | return addressLine2; 62 | } 63 | 64 | public void setAddressLine2(String addressLine2) { 65 | this.addressLine2 = addressLine2; 66 | } 67 | 68 | public String getCity() { 69 | return city; 70 | } 71 | 72 | public void setCity(String city) { 73 | this.city = city; 74 | } 75 | 76 | public String getState() { 77 | return state; 78 | } 79 | 80 | public void setState(String state) { 81 | this.state = state; 82 | } 83 | 84 | public int getPostCode() { 85 | return postCode; 86 | } 87 | 88 | public void setPostCode(int postCode) { 89 | this.postCode = postCode; 90 | } 91 | 92 | public String getSuburb() { 93 | return suburb; 94 | } 95 | 96 | public void setSuburb(String suburb) { 97 | this.suburb = suburb; 98 | } 99 | 100 | @Override 101 | public boolean equals(Object o) { 102 | if (this == o) return true; 103 | if (o == null || getClass() != o.getClass()) return false; 104 | Address address = (Address) o; 105 | return postCode == address.postCode && Objects.equals(addressLine1, address.addressLine1) && 106 | Objects.equals(addressLine2, address.addressLine2) && Objects.equals(suburb, address.suburb) && 107 | Objects.equals(city, address.city) && Objects.equals(state, address.state); 108 | } 109 | 110 | @Override 111 | public int hashCode() { 112 | return Objects.hash(addressLine1, addressLine2, suburb, city, state, postCode); 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "Address{" + 118 | "addressLine1='" + addressLine1 + '\'' + 119 | ", addressLine2='" + addressLine2 + '\'' + 120 | ", suburb='" + suburb + '\'' + 121 | ", city='" + city + '\'' + 122 | ", state='" + state + '\'' + 123 | ", postCode=" + postCode + 124 | '}'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-jakarta/src/main/java/com/oracle/coherence/python/testing/Customer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | 8 | package com.oracle.coherence.python.testing; 9 | 10 | import java.io.Serializable; 11 | import jakarta.json.bind.annotation.JsonbProperty; 12 | 13 | /** 14 | * Class to represent a customer. 15 | * 16 | * @author Tim Middleton 2022-12-22 17 | */ 18 | public class Customer 19 | implements Serializable { 20 | 21 | @JsonbProperty("id") 22 | private int id; 23 | 24 | @JsonbProperty("customerName") 25 | private String customerName; 26 | 27 | @JsonbProperty("homeAddress") 28 | private Address homeAddress; 29 | 30 | @JsonbProperty("postalAddress") 31 | private Address postalAddress; 32 | 33 | @JsonbProperty("customerType") 34 | private String customerType; 35 | 36 | @JsonbProperty("outstandingBalance") 37 | private double outstandingBalance; 38 | 39 | public static final String BRONZE = "BRONZE"; 40 | public static final String SILVER = "SILVER"; 41 | public static final String GOLD = "GOLD"; 42 | 43 | public Customer() {} 44 | 45 | public Customer(int id, String customerName, Address officeAddress, Address postalAddress, String customerType, double outstandingBalance) { 46 | this.id = id; 47 | this.customerName = customerName; 48 | this.homeAddress = officeAddress; 49 | this.postalAddress = postalAddress; 50 | this.customerType = customerType; 51 | this.outstandingBalance = outstandingBalance; 52 | } 53 | 54 | 55 | @Override 56 | public String toString() { 57 | return "Customer{" + 58 | "id=" + id + 59 | ", customerName='" + customerName + '\'' + 60 | ", officeAddress=" + homeAddress + 61 | ", postalAddress=" + postalAddress + 62 | ", customerType='" + customerType + '\'' + 63 | ", outstandingBalance=" + outstandingBalance + 64 | '}'; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object o) { 69 | if (this == o) return true; 70 | if (o == null || getClass() != o.getClass()) return false; 71 | 72 | Customer customer = (Customer) o; 73 | 74 | if (id != customer.id) return false; 75 | if (Double.compare(customer.outstandingBalance, outstandingBalance) != 0) return false; 76 | if (customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) return false; 77 | if (homeAddress != null ? !homeAddress.equals(customer.homeAddress) : customer.homeAddress != null) return false; 78 | if (postalAddress != null ? !postalAddress.equals(customer.postalAddress) : customer.postalAddress != null) return false; 79 | return customerType != null ? customerType.equals(customer.customerType) : customer.customerType == null; 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | int result; 85 | long temp; 86 | result = id; 87 | result = 31 * result + (customerName != null ? customerName.hashCode() : 0); 88 | result = 31 * result + (homeAddress != null ? homeAddress.hashCode() : 0); 89 | result = 31 * result + (postalAddress != null ? postalAddress.hashCode() : 0); 90 | result = 31 * result + (customerType != null ? customerType.hashCode() : 0); 91 | temp = Double.doubleToLongBits(outstandingBalance); 92 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 93 | return result; 94 | } 95 | 96 | public int getId() { 97 | return id; 98 | } 99 | 100 | public void setId(int id) { 101 | this.id = id; 102 | } 103 | 104 | public String getCustomerName() { 105 | return customerName; 106 | } 107 | 108 | public void setCustomerName(String customerName) { 109 | this.customerName = customerName; 110 | } 111 | 112 | public Address getHomeAddress() { 113 | return homeAddress; 114 | } 115 | 116 | public void setHomeAddress(Address homeAddress) { 117 | this.homeAddress = homeAddress; 118 | } 119 | 120 | public Address getPostalAddress() { 121 | return postalAddress; 122 | } 123 | 124 | public void setPostalAddress(Address postalAddress) { 125 | this.postalAddress = postalAddress; 126 | } 127 | 128 | public String getCustomerType() { 129 | return customerType; 130 | } 131 | 132 | public void setCustomerType(String customerType) { 133 | this.customerType = customerType; 134 | } 135 | 136 | public double getOutstandingBalance() { 137 | return outstandingBalance; 138 | } 139 | 140 | public void setOutstandingBalance(double outstandingBalance) { 141 | this.outstandingBalance = outstandingBalance; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-javax/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 4.0.0 14 | 15 | 16 | com.oracle.coherence.python 17 | coherence-python-parent 18 | 1.0.0 19 | ../pom.xml 20 | 21 | 22 | coherence-python-client-data-javax 23 | 24 | Oracle Coherence Python Client Data Javax 25 | coherence-python-client-data-javax 26 | 27 | 28 | 29 | ${coherence.group.id} 30 | coherence-json 31 | ${coherence.version} 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-javax/src/main/java/com/oracle/coherence/python/testing/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | 8 | package com.oracle.coherence.python.testing; 9 | 10 | import javax.json.bind.annotation.JsonbProperty; 11 | import java.io.Serializable; 12 | import java.util.Objects; 13 | 14 | /** 15 | * Class to represent an Australian address. 16 | * 17 | * @author Tim Middleton 2022-12-22 18 | */ 19 | public class Address 20 | implements Serializable { 21 | 22 | @JsonbProperty("addressLine1") 23 | private String addressLine1; 24 | 25 | @JsonbProperty("addressLine2") 26 | private String addressLine2; 27 | 28 | @JsonbProperty("suburb") 29 | private String suburb; 30 | 31 | @JsonbProperty("city") 32 | private String city; 33 | 34 | @JsonbProperty("state") 35 | private String state; 36 | 37 | @JsonbProperty("postcode") 38 | private int postCode; 39 | 40 | public Address() { 41 | } 42 | 43 | public Address(String addressLine1, String addressLine2, String suburb, String city, String state, int postCode) { 44 | this.addressLine1 = addressLine1; 45 | this.addressLine2 = addressLine2; 46 | this.suburb = suburb; 47 | this.city = city; 48 | this.state = state; 49 | this.postCode = postCode; 50 | } 51 | 52 | public String getAddressLine1() { 53 | return addressLine1; 54 | } 55 | 56 | public void setAddressLine1(String addressLine1) { 57 | this.addressLine1 = addressLine1; 58 | } 59 | 60 | public String getAddressLine2() { 61 | return addressLine2; 62 | } 63 | 64 | public void setAddressLine2(String addressLine2) { 65 | this.addressLine2 = addressLine2; 66 | } 67 | 68 | public String getCity() { 69 | return city; 70 | } 71 | 72 | public void setCity(String city) { 73 | this.city = city; 74 | } 75 | 76 | public String getState() { 77 | return state; 78 | } 79 | 80 | public void setState(String state) { 81 | this.state = state; 82 | } 83 | 84 | public int getPostCode() { 85 | return postCode; 86 | } 87 | 88 | public void setPostCode(int postCode) { 89 | this.postCode = postCode; 90 | } 91 | 92 | public String getSuburb() { 93 | return suburb; 94 | } 95 | 96 | public void setSuburb(String suburb) { 97 | this.suburb = suburb; 98 | } 99 | 100 | @Override 101 | public boolean equals(Object o) { 102 | if (this == o) return true; 103 | if (o == null || getClass() != o.getClass()) return false; 104 | Address address = (Address) o; 105 | return postCode == address.postCode && Objects.equals(addressLine1, address.addressLine1) && 106 | Objects.equals(addressLine2, address.addressLine2) && Objects.equals(suburb, address.suburb) && 107 | Objects.equals(city, address.city) && Objects.equals(state, address.state); 108 | } 109 | 110 | @Override 111 | public int hashCode() { 112 | return Objects.hash(addressLine1, addressLine2, suburb, city, state, postCode); 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "Address{" + 118 | "addressLine1='" + addressLine1 + '\'' + 119 | ", addressLine2='" + addressLine2 + '\'' + 120 | ", suburb='" + suburb + '\'' + 121 | ", city='" + city + '\'' + 122 | ", state='" + state + '\'' + 123 | ", postCode=" + postCode + 124 | '}'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/java/coherence-python-client-data-javax/src/main/java/com/oracle/coherence/python/testing/Customer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | 8 | package com.oracle.coherence.python.testing; 9 | 10 | import javax.json.bind.annotation.JsonbProperty; 11 | import java.io.Serializable; 12 | 13 | /** 14 | * Class to represent a customer. 15 | * 16 | * @author Tim Middleton 2022-12-22 17 | */ 18 | public class Customer 19 | implements Serializable { 20 | 21 | @JsonbProperty("id") 22 | private int id; 23 | 24 | @JsonbProperty("customerName") 25 | private String customerName; 26 | 27 | @JsonbProperty("homeAddress") 28 | private Address homeAddress; 29 | 30 | @JsonbProperty("postalAddress") 31 | private Address postalAddress; 32 | 33 | @JsonbProperty("customerType") 34 | private String customerType; 35 | 36 | @JsonbProperty("outstandingBalance") 37 | private double outstandingBalance; 38 | 39 | public static final String BRONZE = "BRONZE"; 40 | public static final String SILVER = "SILVER"; 41 | public static final String GOLD = "GOLD"; 42 | 43 | public Customer() {} 44 | 45 | public Customer(int id, String customerName, Address officeAddress, Address postalAddress, String customerType, double outstandingBalance) { 46 | this.id = id; 47 | this.customerName = customerName; 48 | this.homeAddress = officeAddress; 49 | this.postalAddress = postalAddress; 50 | this.customerType = customerType; 51 | this.outstandingBalance = outstandingBalance; 52 | } 53 | 54 | 55 | @Override 56 | public String toString() { 57 | return "Customer{" + 58 | "id=" + id + 59 | ", customerName='" + customerName + '\'' + 60 | ", officeAddress=" + homeAddress + 61 | ", postalAddress=" + postalAddress + 62 | ", customerType='" + customerType + '\'' + 63 | ", outstandingBalance=" + outstandingBalance + 64 | '}'; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object o) { 69 | if (this == o) return true; 70 | if (o == null || getClass() != o.getClass()) return false; 71 | 72 | Customer customer = (Customer) o; 73 | 74 | if (id != customer.id) return false; 75 | if (Double.compare(customer.outstandingBalance, outstandingBalance) != 0) return false; 76 | if (customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) return false; 77 | if (homeAddress != null ? !homeAddress.equals(customer.homeAddress) : customer.homeAddress != null) return false; 78 | if (postalAddress != null ? !postalAddress.equals(customer.postalAddress) : customer.postalAddress != null) return false; 79 | return customerType != null ? customerType.equals(customer.customerType) : customer.customerType == null; 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | int result; 85 | long temp; 86 | result = id; 87 | result = 31 * result + (customerName != null ? customerName.hashCode() : 0); 88 | result = 31 * result + (homeAddress != null ? homeAddress.hashCode() : 0); 89 | result = 31 * result + (postalAddress != null ? postalAddress.hashCode() : 0); 90 | result = 31 * result + (customerType != null ? customerType.hashCode() : 0); 91 | temp = Double.doubleToLongBits(outstandingBalance); 92 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 93 | return result; 94 | } 95 | 96 | public int getId() { 97 | return id; 98 | } 99 | 100 | public void setId(int id) { 101 | this.id = id; 102 | } 103 | 104 | public String getCustomerName() { 105 | return customerName; 106 | } 107 | 108 | public void setCustomerName(String customerName) { 109 | this.customerName = customerName; 110 | } 111 | 112 | public Address getHomeAddress() { 113 | return homeAddress; 114 | } 115 | 116 | public void setHomeAddress(Address homeAddress) { 117 | this.homeAddress = homeAddress; 118 | } 119 | 120 | public Address getPostalAddress() { 121 | return postalAddress; 122 | } 123 | 124 | public void setPostalAddress(Address postalAddress) { 125 | this.postalAddress = postalAddress; 126 | } 127 | 128 | public String getCustomerType() { 129 | return customerType; 130 | } 131 | 132 | public void setCustomerType(String customerType) { 133 | this.customerType = customerType; 134 | } 135 | 136 | public double getOutstandingBalance() { 137 | return outstandingBalance; 138 | } 139 | 140 | public void setOutstandingBalance(double outstandingBalance) { 141 | this.outstandingBalance = outstandingBalance; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 4.0.0 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-compiler-plugin 19 | 20 | 9 21 | 9 22 | 23 | 24 | 25 | 26 | 27 | 28 | com.oracle.coherence.python 29 | coherence-python-parent 30 | 1.0.0 31 | ../pom.xml 32 | 33 | 34 | coherence-python-test 35 | 36 | Oracle Coherence Python Client Test 37 | coherence-python-test 38 | 39 | 40 | 41 | jakarta 42 | 43 | false 44 | 45 | 46 | 47 | com.oracle.coherence.python 48 | coherence-python-client-data-jakarta 49 | 1.0.0 50 | 51 | 52 | 53 | 54 | 55 | javax 56 | 57 | 58 | 59 | . 60 | 61 | 62 | 63 | 64 | com.oracle.coherence.python 65 | coherence-python-client-data-javax 66 | 1.0.0 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ${coherence.group.id} 75 | coherence 76 | ${coherence.version} 77 | 78 | 79 | ${coherence.group.id} 80 | coherence-management 81 | ${coherence.version} 82 | 83 | 84 | ${coherence.group.id} 85 | coherence-grpc-proxy 86 | ${coherence.version} 87 | 88 | 89 | ${coherence.group.id} 90 | coherence-json 91 | ${coherence.version} 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/src/main/java/com/oracle/coherence/python/testing/LongRunningProcessor.java: -------------------------------------------------------------------------------- 1 | package com.oracle.coherence.python.testing; 2 | 3 | 4 | import com.tangosol.util.Base; 5 | import com.tangosol.util.InvocableMap; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | 10 | public class LongRunningProcessor 11 | implements InvocableMap.EntryProcessor 12 | { 13 | public Void process(InvocableMap.Entry entry) 14 | { 15 | Base.sleep(5000); 16 | return null; 17 | } 18 | 19 | public Map processAll(Set> setEntries) 20 | { 21 | Base.sleep(5000); 22 | return InvocableMap.EntryProcessor.super.processAll(setEntries); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/src/main/java/com/oracle/coherence/python/testing/SimpleCacheLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | 8 | package com.oracle.coherence.python.testing; 9 | 10 | import com.tangosol.net.cache.CacheLoader; 11 | 12 | /** 13 | * A simple {@link CacheLoader} implementation to demonstrate basic functionality. 14 | * 15 | * @author Tim Middleton 2020.02.17 16 | */ 17 | public class SimpleCacheLoader 18 | implements CacheLoader { 19 | 20 | private String cacheName; 21 | 22 | /** 23 | * Constructs a {@link SimpleCacheLoader}. 24 | * 25 | * @param cacheName cache name 26 | */ 27 | public SimpleCacheLoader(String cacheName) { 28 | this.cacheName = cacheName; 29 | } 30 | 31 | /** 32 | * An implementation of a load which returns the String "Number " + the key. 33 | * 34 | * @param key key whose associated value is to be returned 35 | * @return the value for the given key 36 | */ 37 | @Override 38 | public String load(Integer key) { // <3> 39 | return "Number " + key; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/src/main/java/com/oracle/coherence/python/testing/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Oracle and/or its affiliates. 3 | * Licensed under the Universal Permissive License v 1.0 as shown at 4 | * https://oss.oracle.com/licenses/upl. 5 | */ 6 | 7 | /** 8 | * Classes to support testing Go Client. 9 | */ 10 | package com.oracle.coherence.python.testing; 11 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/src/main/resources/META-INF/type-aliases.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | test.customer=com.oracle.coherence.python.testing.Customer 6 | test.address=com.oracle.coherence.python.testing.Address 7 | test.longrunning=com.oracle.coherence.python.testing.LongRunningProcessor 8 | -------------------------------------------------------------------------------- /tests/java/coherence-python-test/src/main/resources/test-cache-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | 12 | 13 | * 14 | distributed-scheme 15 | 16 | 17 | canary 18 | canary-scheme 19 | 20 | 21 | preload 22 | simple-cache-loader 23 | 24 | 25 | touch 26 | touch-scheme 27 | 28 | 29 | 30 | 31 | 32 | distributed-scheme 33 | PartitionedCache 34 | true 35 | 36 | 31 37 | 38 | 39 | BINARY 40 | 41 | 42 | true 43 | 44 | 45 | 46 | touch-scheme 47 | PartitionedCacheTouch 48 | true 49 | 50 | 31 51 | 52 | 53 | BINARY 54 | 10s 55 | 56 | 57 | true 58 | 59 | 60 | 61 | canary-scheme 62 | CanaryService 63 | true 64 | 65 | 31 66 | 67 | 68 | 69 | true 70 | 71 | 72 | 73 | simple-cache-loader 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | com.oracle.coherence.python.testing.SimpleCacheLoader 82 | 83 | 84 | java.lang.String 85 | {cache-name} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tests/logging.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | [loggers] 6 | keys=root,coherence,coherence-test 7 | 8 | [handlers] 9 | keys=consoleHandler 10 | 11 | [formatters] 12 | keys=sampleFormatter 13 | 14 | [logger_root] 15 | level=INFO 16 | handlers=consoleHandler 17 | 18 | [logger_coherence] 19 | level=INFO 20 | handlers=consoleHandler 21 | qualname=coherence 22 | propagate=0 23 | 24 | [logger_coherence-test] 25 | level=INFO 26 | handlers=consoleHandler 27 | qualname=coherence-test 28 | propagate=0 29 | 30 | [handler_consoleHandler] 31 | class=StreamHandler 32 | level=INFO 33 | formatter=sampleFormatter 34 | args=(sys.stdout,) 35 | 36 | [formatter_sampleFormatter] 37 | format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 38 | -------------------------------------------------------------------------------- /tests/person.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from __future__ import annotations 6 | 7 | from coherence import NamedMap 8 | from tests.address import Address 9 | 10 | 11 | class Person: 12 | def __init__(self, name: str, gender: str, age: int, weight: float, address: Address, sports: list[str]): 13 | self.name = name 14 | self.gender = gender 15 | self.age = age 16 | self.weight = weight 17 | self.address = address 18 | self.sports = sports 19 | 20 | def __str__(self) -> str: 21 | return str(self.__dict__) 22 | 23 | def __eq__(self, other: object) -> bool: 24 | if not isinstance(other, Person): 25 | return NotImplemented 26 | return self.name == other.name 27 | 28 | def __hash__(self) -> int: 29 | return hash(self.name) 30 | 31 | @classmethod 32 | async def populate_named_map(cls, cache: NamedMap[str, Person]) -> None: 33 | await cache.put(Person.pat().name, Person.pat()) 34 | await cache.put(Person.paula().name, Person.paula()) 35 | await cache.put(Person.andy().name, Person.andy()) 36 | await cache.put(Person.alice().name, Person.alice()) 37 | await cache.put(Person.jim().name, Person.jim()) 38 | await cache.put(Person.fred().name, Person.fred()) 39 | await cache.put(Person.fiona().name, Person.fiona()) 40 | 41 | @classmethod 42 | def create_person( 43 | cls, name: str, gender: str, age: int, weight: float, address: Address, sports: list[str] 44 | ) -> Person: 45 | return Person(name, gender, age, weight, address, sports) 46 | 47 | @classmethod 48 | def fred(cls) -> Person: 49 | addr = Address.address("1597 Olive Street", "San Francisco", "CA", 94102, "USA") 50 | return cls.create_person("Fred", "Male", 58, 185.5, addr, ["soccer", "tennis", "cricket"]) 51 | 52 | @classmethod 53 | def fiona(cls) -> Person: 54 | addr = Address.address("2382 Palm Ave", "Daly City", "CA", 94014, "USA") 55 | return cls.create_person("Fiona", "Female", 29, 118.5, addr, ["tennis", "hiking"]) 56 | 57 | @classmethod 58 | def pat(cls) -> Person: 59 | addr = Address.address("2038 Helford Lane", "Carmel", "IN", 46032, "USA") 60 | return cls.create_person("Pat", "Male", 62, 205.0, addr, ["golf", "pool", "curling"]) 61 | 62 | @classmethod 63 | def paula(cls) -> Person: 64 | addr = Address.address("4218 Daniel St", "Champaign", "IL", 61820, "USA") 65 | return cls.create_person("Paula", "Female", 35, 125.0, addr, ["swimming", "golf", "skiing"]) 66 | 67 | @classmethod 68 | def andy(cls) -> Person: 69 | addr = Address.address("1228 West Ave", "Miami", "FL", 33139, "USA") 70 | return cls.create_person("Andy", "Male", 25, 155.0, addr, ["soccer", "triathlon", "tennis"]) 71 | 72 | @classmethod 73 | def alice(cls) -> Person: 74 | addr = Address.address("2208 4th Ave", "Phoenix", "AZ", 85003, "USA") 75 | return cls.create_person("Alice", "Female", 22, 110.0, addr, ["golf", "running", "tennis"]) 76 | 77 | @classmethod 78 | def jim(cls) -> Person: 79 | addr = Address.address("37 Bowdoin St", "Boston", "MA", 2114, "USA") 80 | return cls.create_person("Jim", "Male", 36, 175.5, addr, ["golf", "football", "badminton"]) 81 | -------------------------------------------------------------------------------- /tests/scripts/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright (c) 2022, 2025, Oracle and/or its affiliates. 5 | # Licensed under the Universal Permissive License v 1.0 as shown at 6 | # https://oss.oracle.com/licenses/upl. 7 | # 8 | 9 | # Run compatability tests 10 | set -e 11 | 12 | # Set the following to include long running streaming tests 13 | # INCLUDE_LONG_RUNNING=true 14 | 15 | COH_VER=$1 16 | if [ -z "${COH_VER}" ] ; then 17 | echo "Please provide Coherence version" 18 | exit 1 19 | fi 20 | 21 | BASE_IMAGE=$2 22 | if [ -z "${BASE_IMAGE}" ] ; then 23 | echo "Please provide Base image" 24 | exit 1 25 | fi 26 | 27 | PROFILE_STR=$3 28 | if [ -z "${PROFILE_STR}" ] ; then 29 | echo "Please provide Profile string" 30 | exit 1 31 | fi 32 | 33 | if [[ "${COH_VER}" =~ "22.06" ]] || [[ "${COH_VER}" =~ "14.1.1-2206" ]] || [[ "${COH_VER}" =~ "14.1.2" ]]; then 34 | echo "Coherence CE ${COH_VER}" 35 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 \ 36 | COHERENCE_VERSION=$COH_VER \ 37 | COHERENCE_BASE_IMAGE=$BASE_IMAGE \ 38 | PROFILES=$PROFILE_STR \ 39 | make clean test-cluster-shutdown remove-app-images build-test-images test-cluster-startup just-wait test 40 | else 41 | echo "Coherence CE ${COH_VER}" 42 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 \ 43 | COHERENCE_VERSION=$COH_VER \ 44 | COHERENCE_BASE_IMAGE=$BASE_IMAGE \ 45 | PROFILES=$PROFILE_STR \ 46 | make clean test-cluster-shutdown remove-app-images build-test-images test-cluster-startup just-wait test-with-ai 47 | fi 48 | 49 | # Run tests with SSL 50 | if [[ "${COH_VER}" =~ "22.06" ]] || [[ "${COH_VER}" =~ "14.1.1-2206" ]] || [[ "${COH_VER}" =~ "14.1.2" ]]; then 51 | echo "Coherence CE ${COH_VER} with SSL" 52 | RUN_SECURE=true COHERENCE_IGNORE_INVALID_CERTS=true \ 53 | COHERENCE_TLS_CERTS_PATH=$(pwd)/tests/utils/certs/guardians-ca.crt \ 54 | COHERENCE_TLS_CLIENT_CERT=$(pwd)/tests/utils/certs/star-lord.crt \ 55 | COHERENCE_TLS_CLIENT_KEY=$(pwd)/tests/utils/certs/star-lord.pem \ 56 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 \ 57 | COHERENCE_VERSION=$COH_VER \ 58 | COHERENCE_BASE_IMAGE=$BASE_IMAGE \ 59 | PROFILES=$PROFILE_STR,secure \ 60 | make clean certs test-cluster-shutdown remove-app-images \ 61 | build-test-images test-cluster-startup just-wait test 62 | else 63 | echo "Coherence CE ${COH_VER} with SSL" 64 | RUN_SECURE=true COHERENCE_IGNORE_INVALID_CERTS=true \ 65 | COHERENCE_TLS_CERTS_PATH=$(pwd)/tests/utils/certs/guardians-ca.crt \ 66 | COHERENCE_TLS_CLIENT_CERT=$(pwd)/tests/utils/certs/star-lord.crt \ 67 | COHERENCE_TLS_CLIENT_KEY=$(pwd)/tests/utils/certs/star-lord.pem \ 68 | COHERENCE_CLIENT_REQUEST_TIMEOUT=180.0 \ 69 | COHERENCE_VERSION=$COH_VER \ 70 | COHERENCE_BASE_IMAGE=$BASE_IMAGE \ 71 | PROFILES=$PROFILE_STR,secure \ 72 | make clean certs test-cluster-shutdown remove-app-images \ 73 | build-test-images test-cluster-startup just-wait test-with-ai 74 | fi 75 | -------------------------------------------------------------------------------- /tests/scripts/startup-clusters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright (c) 2022, 2023, Oracle and/or its affiliates. 5 | # Licensed under the Universal Permissive License v 1.0 as shown at 6 | # https://oss.oracle.com/licenses/upl. 7 | # 8 | 9 | # Startup Coherence clusters for use with discovery tests 10 | # $1 = coherence jar 11 | # $2 = logs dir 12 | # $3 = cluster port 13 | 14 | set -e 15 | 16 | LOGSDIR=$1 17 | CLUSTER_PORT=$2 18 | COHERENCE_GROUP_ID=$3 19 | COHERENCE_VERSION=$4 20 | TAG="shutMeDownPlease" 21 | CLUSTERS=3 22 | 23 | PROFILE="" 24 | if [ "$COHERENCE_GROUP_ID" == "com.oracle.coherence" ] ; then 25 | PROFILE="-P commercial" 26 | fi 27 | 28 | COHERENCE_STARTUP="-Dcoherence.wka=127.0.0.1 -Dcoherence.ttl=0 -Dcoherence.clusterport=${CLUSTER_PORT} -D${TAG} -Dcoherence.management.http=all -Dcoherence.management.http.port=0" 29 | 30 | set -x 31 | echo "Generating Classpath..." 32 | CP=`mvn -f java/coherence-go-test dependency:build-classpath -Dcoherence.group.id=${COHERENCE_GROUP_ID} -Dcoherence.version=${COHERENCE_VERSION} ${PROFILE} | sed -ne '/Dependencies classpath/,$ p' | sed '/INFO/d'` 33 | 34 | echo "Starting $CLUSTERS clusters..." 35 | for i in $(seq 1 $CLUSTERS) 36 | do 37 | cluster=cluster${i} 38 | echo "Starting $cluster ..." 39 | java ${COHERENCE_STARTUP} -Dcoherence.cluster=$cluster -cp ${CP} com.tangosol.net.DefaultCacheServer > ${LOGSDIR}/${cluster}.log 2>&1 & 40 | sleep 3 41 | done 42 | 43 | sleep 10 44 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | -------------------------------------------------------------------------------- /tests/unit/test_cache_options.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from coherence.client import CacheOptions 4 | from coherence.local_cache import NearCacheOptions 5 | 6 | 7 | def test_cache_options_default_expiry() -> None: 8 | options: CacheOptions = CacheOptions(-10) 9 | assert options.default_expiry == -1 10 | 11 | options = CacheOptions(10000) 12 | assert options.default_expiry == 10000 13 | 14 | 15 | def test_near_cache_options_no_explicit_params() -> None: 16 | with pytest.raises(ValueError) as err: 17 | NearCacheOptions() 18 | 19 | assert str(err.value) == "at least one option must be specified" 20 | 21 | 22 | def test_near_cache_options_negative_units() -> None: 23 | message: str = "values for high_units and high_units_memory must be positive" 24 | 25 | with pytest.raises(ValueError) as err: 26 | NearCacheOptions(high_units=-1) 27 | 28 | assert str(err.value) == message 29 | 30 | with pytest.raises(ValueError) as err: 31 | NearCacheOptions(high_units_memory=-1) 32 | 33 | assert str(err.value) == message 34 | 35 | 36 | def test_near_cache_options_both_units() -> None: 37 | message: str = "high_units and high_units_memory cannot be used together; specify one or the other" 38 | 39 | with pytest.raises(ValueError) as err: 40 | NearCacheOptions(high_units=1000, high_units_memory=10000) 41 | 42 | assert str(err.value) == message 43 | 44 | with pytest.raises(ValueError) as err: 45 | NearCacheOptions(ttl=10000, high_units=1000, high_units_memory=10000) 46 | 47 | assert str(err.value) == message 48 | 49 | 50 | def test_near_cache_options_prune_factor() -> None: 51 | message: str = "prune_factor must be between .1 and 1" 52 | 53 | with pytest.raises(ValueError) as err: 54 | NearCacheOptions(high_units=100, prune_factor=-1) 55 | 56 | assert str(err.value) == message 57 | 58 | with pytest.raises(ValueError) as err: 59 | NearCacheOptions(high_units=100, prune_factor=0) 60 | 61 | assert str(err.value) == message 62 | 63 | with pytest.raises(ValueError) as err: 64 | NearCacheOptions(high_units=100, prune_factor=0.05) 65 | 66 | assert str(err.value) == message 67 | 68 | with pytest.raises(ValueError) as err: 69 | NearCacheOptions(high_units=100, prune_factor=1.001) 70 | 71 | assert str(err.value) == message 72 | 73 | 74 | def test_near_cache_options_str() -> None: 75 | options: NearCacheOptions = NearCacheOptions(high_units=100) 76 | assert str(options) == "NearCacheOptions(ttl=0ms, high-units=100, high-units-memory=0, prune-factor=0.80)" 77 | 78 | options = NearCacheOptions(high_units=100, ttl=1000) 79 | assert str(options) == "NearCacheOptions(ttl=1000ms, high-units=100, high-units-memory=0, prune-factor=0.80)" 80 | 81 | options = NearCacheOptions(high_units_memory=100 * 1024) 82 | assert str(options) == "NearCacheOptions(ttl=0ms, high-units=0, high-units-memory=102400, prune-factor=0.80)" 83 | 84 | options = NearCacheOptions(high_units_memory=100 * 1024, prune_factor=0.25) 85 | assert str(options) == "NearCacheOptions(ttl=0ms, high-units=0, high-units-memory=102400, prune-factor=0.25)" 86 | 87 | 88 | def test_near_cache_eq() -> None: 89 | options: NearCacheOptions = NearCacheOptions(high_units=100) 90 | options2: NearCacheOptions = NearCacheOptions(high_units=100, ttl=1000) 91 | options3: NearCacheOptions = NearCacheOptions(high_units=100) 92 | 93 | assert options == options 94 | assert options != options2 95 | assert options == options3 96 | assert options != "some string" 97 | 98 | 99 | def test_near_cache_options_ttl() -> None: 100 | options = NearCacheOptions(ttl=1000) 101 | assert options.ttl == 1000 102 | 103 | # ensure minimum can be set 104 | options = NearCacheOptions(ttl=250) 105 | assert options.ttl == 250 106 | 107 | 108 | def test_near_cache_ttl_negative() -> None: 109 | with pytest.raises(ValueError) as err: 110 | NearCacheOptions(ttl=-1) 111 | 112 | assert str(err.value) == "ttl cannot be less than zero" 113 | 114 | with pytest.raises(ValueError) as err: 115 | NearCacheOptions(ttl=100) 116 | 117 | assert str(err.value) == "ttl has 1/4 second resolution; minimum TTL is 250" 118 | 119 | 120 | def test_near_cache_options_high_units() -> None: 121 | options: NearCacheOptions = NearCacheOptions(high_units=10000) 122 | assert options.high_units == 10000 123 | 124 | 125 | def test_near_cache_options_high_units_memory() -> None: 126 | options: NearCacheOptions = NearCacheOptions(high_units_memory=10000) 127 | assert options._high_units_memory == 10000 128 | 129 | 130 | def test_cache_options_str() -> None: 131 | options: CacheOptions = CacheOptions(10000) 132 | assert str(options) == "CacheOptions(default-expiry=10000)" 133 | 134 | options = CacheOptions(5000, NearCacheOptions(high_units=10000)) 135 | assert ( 136 | str(options) == "CacheOptions(default-expiry=5000, near-cache-options=NearCacheOptions(ttl=0ms," 137 | " high-units=10000, high-units-memory=0, prune-factor=0.80))" 138 | ) 139 | 140 | 141 | def test_cache_options_eq() -> None: 142 | options: CacheOptions = CacheOptions(10000) 143 | options2: CacheOptions = CacheOptions(10000) 144 | options3: CacheOptions = CacheOptions(1000) 145 | 146 | assert options == options 147 | assert options == options2 148 | assert options != options3 149 | 150 | options = CacheOptions(10000, NearCacheOptions(high_units=10000)) 151 | options2 = CacheOptions(10000, NearCacheOptions(high_units=10000)) 152 | options3 = CacheOptions(10000, NearCacheOptions(high_units=1000)) 153 | 154 | assert options == options 155 | assert options == options2 156 | assert options != options3 157 | -------------------------------------------------------------------------------- /tests/unit/test_environment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | import pytest 6 | 7 | from coherence import Options, TlsOptions 8 | 9 | 10 | def test_options_address(monkeypatch: pytest.MonkeyPatch) -> None: 11 | try: 12 | monkeypatch.delenv(name=Options.ENV_SERVER_ADDRESS, raising=False) 13 | options: Options = Options() 14 | assert options.address == "localhost:1408" 15 | 16 | custom_address: str = "acme.com:1409" 17 | options = Options(address=custom_address) 18 | assert options.address == custom_address 19 | 20 | custom_address = "acme.com:1409" 21 | monkeypatch.setenv(name=Options.ENV_SERVER_ADDRESS, value=custom_address) 22 | options = Options() 23 | assert options.address == custom_address 24 | 25 | options = Options(address="127.0.0.1:9000") 26 | assert options.address == custom_address 27 | finally: 28 | monkeypatch.undo() 29 | 30 | 31 | def test_options_req_timeout(monkeypatch: pytest.MonkeyPatch) -> None: 32 | try: 33 | monkeypatch.delenv(name=Options.ENV_REQUEST_TIMEOUT, raising=False) 34 | options: Options = Options() 35 | assert options.request_timeout_seconds == 30 36 | 37 | options = Options(request_timeout_seconds=15) 38 | assert options.request_timeout_seconds == 15 39 | 40 | monkeypatch.setenv(Options.ENV_REQUEST_TIMEOUT, "35") 41 | options = Options() 42 | assert options.request_timeout_seconds == 35 43 | 44 | options = Options(request_timeout_seconds=15) 45 | assert options.request_timeout_seconds == 35 46 | finally: 47 | monkeypatch.undo() 48 | 49 | 50 | def test_options_ready_timeout(monkeypatch: pytest.MonkeyPatch) -> None: 51 | try: 52 | monkeypatch.delenv(name=Options.ENV_READY_TIMEOUT, raising=False) 53 | options: Options = Options() 54 | assert options.ready_timeout_seconds == 0 55 | 56 | options = Options(ready_timeout_seconds=15) 57 | assert options.ready_timeout_seconds == 15 58 | 59 | monkeypatch.setenv(Options.ENV_READY_TIMEOUT, "35") 60 | options = Options() 61 | assert options.ready_timeout_seconds == 35 62 | 63 | options = Options(ready_timeout_seconds=15) 64 | assert options.ready_timeout_seconds == 35 65 | finally: 66 | monkeypatch.undo() 67 | 68 | 69 | def test_disconnect_timeout(monkeypatch: pytest.MonkeyPatch) -> None: 70 | try: 71 | monkeypatch.delenv(name=Options.ENV_SESSION_DISCONNECT_TIMEOUT, raising=False) 72 | options: Options = Options() 73 | assert options.session_disconnect_timeout_seconds == 30 74 | 75 | options = Options(session_disconnect_seconds=15) 76 | assert options.session_disconnect_timeout_seconds == 15 77 | 78 | monkeypatch.setenv(Options.ENV_SESSION_DISCONNECT_TIMEOUT, "35") 79 | options = Options() 80 | assert options.session_disconnect_timeout_seconds == 35 81 | 82 | options = Options(ready_timeout_seconds=15) 83 | assert options.session_disconnect_timeout_seconds == 35 84 | finally: 85 | monkeypatch.undo() 86 | 87 | 88 | def test_tls_options(monkeypatch: pytest.MonkeyPatch) -> None: 89 | try: 90 | monkeypatch.delenv(name=TlsOptions.ENV_CA_CERT, raising=False) 91 | monkeypatch.delenv(name=TlsOptions.ENV_CLIENT_CERT, raising=False) 92 | monkeypatch.delenv(name=TlsOptions.ENV_CLIENT_KEY, raising=False) 93 | 94 | tls_options: TlsOptions = TlsOptions() 95 | assert tls_options.enabled is False 96 | assert tls_options.ca_cert_path is None 97 | assert tls_options.client_cert_path is None 98 | assert tls_options.client_key_path is None 99 | 100 | ca_cert_path: str = "/tmp/ca.pem" 101 | client_cert_path: str = "/tmp/client.pem" 102 | client_key_path: str = "/tmp/client.key" 103 | ca_cert_path2: str = "/tmp/ca2.pem" 104 | client_cert_path2: str = "/tmp/client2.pem" 105 | client_key_path2: str = "/tmp/client2.key" 106 | 107 | tls_options = TlsOptions( 108 | enabled=True, ca_cert_path=ca_cert_path, client_cert_path=client_cert_path, client_key_path=client_key_path 109 | ) 110 | assert tls_options.enabled 111 | assert tls_options.ca_cert_path == ca_cert_path 112 | assert tls_options.client_cert_path == client_cert_path 113 | assert tls_options.client_key_path == client_key_path 114 | 115 | monkeypatch.setenv(name=TlsOptions.ENV_CA_CERT, value=ca_cert_path) 116 | monkeypatch.setenv(name=TlsOptions.ENV_CLIENT_CERT, value=client_cert_path) 117 | monkeypatch.setenv(name=TlsOptions.ENV_CLIENT_KEY, value=client_key_path) 118 | 119 | tls_options = TlsOptions() 120 | assert tls_options.enabled is False 121 | assert tls_options.ca_cert_path == ca_cert_path 122 | assert tls_options.client_cert_path == client_cert_path 123 | assert tls_options.client_key_path == client_key_path 124 | 125 | tls_options = TlsOptions( 126 | enabled=True, 127 | ca_cert_path=ca_cert_path2, 128 | client_cert_path=client_cert_path2, 129 | client_key_path=client_key_path2, 130 | ) 131 | assert tls_options.enabled 132 | assert tls_options.ca_cert_path == ca_cert_path 133 | assert tls_options.client_cert_path == client_cert_path 134 | assert tls_options.client_key_path == client_key_path 135 | 136 | monkeypatch.delenv(name=TlsOptions.ENV_CA_CERT) 137 | tls_options = TlsOptions( 138 | enabled=True, 139 | ca_cert_path=ca_cert_path2, 140 | client_cert_path=client_cert_path2, 141 | client_key_path=client_key_path2, 142 | ) 143 | assert tls_options.enabled 144 | assert tls_options.ca_cert_path == ca_cert_path2 145 | assert tls_options.client_cert_path == client_cert_path 146 | assert tls_options.client_key_path == client_key_path 147 | 148 | monkeypatch.delenv(name=TlsOptions.ENV_CLIENT_CERT) 149 | tls_options = TlsOptions( 150 | enabled=True, 151 | ca_cert_path=ca_cert_path2, 152 | client_cert_path=client_cert_path2, 153 | client_key_path=client_key_path2, 154 | ) 155 | assert tls_options.enabled 156 | assert tls_options.ca_cert_path == ca_cert_path2 157 | assert tls_options.client_cert_path == client_cert_path2 158 | assert tls_options.client_key_path == client_key_path 159 | 160 | monkeypatch.delenv(name=TlsOptions.ENV_CLIENT_KEY) 161 | tls_options = TlsOptions( 162 | enabled=True, 163 | ca_cert_path=ca_cert_path2, 164 | client_cert_path=client_cert_path2, 165 | client_key_path=client_key_path2, 166 | ) 167 | assert tls_options.enabled 168 | assert tls_options.ca_cert_path == ca_cert_path2 169 | assert tls_options.client_cert_path == client_cert_path2 170 | assert tls_options.client_key_path == client_key_path2 171 | finally: 172 | monkeypatch.undo() 173 | -------------------------------------------------------------------------------- /tests/unit/test_extractors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, 2024, Oracle and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | from typing import Any, List, Optional, cast 6 | 7 | import pytest 8 | 9 | from coherence import Extractors 10 | from coherence.extractor import ( 11 | AbstractCompositeExtractor, 12 | ChainedExtractor, 13 | MultiExtractor, 14 | UniversalExtractor, 15 | ValueExtractor, 16 | ) 17 | 18 | 19 | def test_extract_simple() -> None: 20 | result = Extractors.extract("simple") 21 | _validate_universal(result, "simple") 22 | 23 | 24 | def test_extract_chained() -> None: 25 | result = Extractors.extract("prop1.prop2.prop3") 26 | _validate_composite(result, ChainedExtractor, 3) 27 | 28 | _validate_universal(result.extractors[0], "prop1") 29 | _validate_universal(result.extractors[1], "prop2") 30 | _validate_universal(result.extractors[2], "prop3") 31 | 32 | 33 | def test_extract_multi() -> None: 34 | result = Extractors.extract("prop1,prop2,prop3") 35 | _validate_composite(result, MultiExtractor, 3) 36 | 37 | _validate_universal(result.extractors[0], "prop1") 38 | _validate_universal(result.extractors[1], "prop2") 39 | _validate_universal(result.extractors[2], "prop3") 40 | 41 | 42 | def test_extract_method_no_parens() -> None: 43 | args = ["arg1", 10] 44 | result = Extractors.extract("length", args) 45 | _validate_universal(result, "length()", args) 46 | 47 | 48 | def test_extract_method_with_parens() -> None: 49 | args = ["arg1", 10] 50 | result = Extractors.extract("length()", args) 51 | _validate_universal(result, "length()", args) 52 | 53 | 54 | def test_extract_no_expression() -> None: 55 | with pytest.raises(ValueError): 56 | Extractors.extract(None) 57 | 58 | 59 | def test_compose() -> None: 60 | result = Extractors.extract("prop1") 61 | result = result.compose(Extractors.extract("prop2")) 62 | 63 | _validate_composite(result, ChainedExtractor, 2) 64 | 65 | _validate_universal(result.extractors[0], "prop2") 66 | _validate_universal(result.extractors[1], "prop1") 67 | 68 | 69 | def test_compose_no_extractor_arg() -> None: 70 | result = Extractors.extract("prop1") 71 | with pytest.raises(ValueError): 72 | result.compose(None) 73 | 74 | 75 | def test_and_then_no_extractor_arg() -> None: 76 | result = Extractors.extract("prop1") 77 | with pytest.raises(ValueError): 78 | result.and_then(None) 79 | 80 | 81 | def test_and_then() -> None: 82 | result = Extractors.extract("prop1") 83 | result = result.and_then(Extractors.extract("prop2")) 84 | 85 | _validate_composite(result, ChainedExtractor, 2) 86 | 87 | _validate_universal(result.extractors[0], "prop1") 88 | _validate_universal(result.extractors[1], "prop2") 89 | 90 | 91 | def _validate_universal(extractor: ValueExtractor[Any, Any], expr: str, params: Optional[List[Any]] = None) -> None: 92 | assert extractor is not None 93 | assert isinstance(extractor, UniversalExtractor) 94 | 95 | extractor_local = cast(UniversalExtractor, extractor) 96 | assert extractor_local.name == expr 97 | assert extractor_local.params == params 98 | 99 | 100 | def _validate_composite(extractor: ValueExtractor[Any, Any], expected_type: type, length: int) -> None: 101 | assert extractor is not None 102 | assert isinstance(extractor, expected_type) 103 | 104 | extractor_local = cast(AbstractCompositeExtractor, extractor) 105 | assert extractor_local.extractors is not None 106 | assert len(extractor_local.extractors) == length 107 | -------------------------------------------------------------------------------- /tests/utils/docker-compose-2-members.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022, 2023, Oracle Corporation and/or its affiliates. 2 | # Licensed under the Universal Permissive License v 1.0 as shown at 3 | # https://oss.oracle.com/licenses/upl. 4 | 5 | version: "3.5" 6 | services: 7 | coherence1: 8 | hostname: server1 9 | networks: 10 | coherence: 11 | aliases: 12 | - server1 13 | image: ghcr.io/oracle/coherence-python-test-1:1.0.0 14 | environment: 15 | - COHERENCE_WKA=server1 16 | ports: 17 | - 30000:30000 18 | - 1408:1408 19 | - 9612:9612 20 | - 8080:8080 21 | volumes: 22 | - ./certs:/certs 23 | 24 | coherence2: 25 | hostname: server2 26 | networks: 27 | coherence: 28 | aliases: 29 | - server2 30 | image: ghcr.io/oracle/coherence-python-test-2:1.0.0 31 | environment: 32 | - COHERENCE_WKA=server1 33 | ports: 34 | - 9613:9613 35 | 36 | networks: 37 | coherence: 38 | --------------------------------------------------------------------------------