feat: support named parameters in SQL queries and enhance type conver… #494
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test Python Bindings | |
| on: | |
| # Run on push to bindings/python/ directory | |
| push: | |
| paths: | |
| - 'bindings/python/**' | |
| - '.github/workflows/test-python-bindings.yml' | |
| # Run on pull request affecting bindings/python/ | |
| pull_request: | |
| paths: | |
| - 'bindings/python/**' | |
| - '.github/workflows/test-python-bindings.yml' | |
| # Run after release workflow completes | |
| workflow_run: | |
| workflows: ["Release"] | |
| types: [completed] | |
| # Allow being called by other workflows (e.g., release workflow) | |
| workflow_call: | |
| inputs: | |
| build-version: | |
| description: "Override package version (PEP 440) for build.sh" | |
| required: false | |
| type: string | |
| # Allow manual trigger | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| bandit: | |
| name: Bandit security scan (bindings/python) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Bandit | |
| run: python -m pip install "bandit==1.9.4" | |
| - name: Run Bandit on src and tests (must be clean) | |
| working-directory: bindings/python | |
| run: python -m bandit -c pyproject.toml -r src tests --severity-level low --confidence-level low | |
| - name: Run Bandit on examples (must be clean at medium+/high-confidence) | |
| working-directory: bindings/python | |
| run: python -m bandit -c pyproject.toml -r examples --severity-level medium --confidence-level high | |
| # First job: Download ArcadeDB JARs (platform-agnostic) | |
| download-jars: | |
| name: Download ArcadeDB JARs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 | |
| - name: Download JARs from ArcadeDB Docker image | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| # Detect ArcadeDB version from pom.xml (Docker format: X.Y.Z-SNAPSHOT) | |
| ARCADEDB_TAG=$(python3 scripts/extract_version.py --format=docker) | |
| echo "📌 ArcadeDB version: $ARCADEDB_TAG" | |
| # Download JARs from official Docker image | |
| echo "📦 Downloading JARs from arcadedata/arcadedb:$ARCADEDB_TAG..." | |
| # Create output directory | |
| mkdir -p src/arcadedb_embedded/jars | |
| # Create a temporary container and copy JARs from it | |
| CONTAINER_ID=$(docker create arcadedata/arcadedb:$ARCADEDB_TAG) | |
| docker cp $CONTAINER_ID:/home/arcadedb/lib/. src/arcadedb_embedded/jars/ | |
| docker rm $CONTAINER_ID | |
| # Verify JARs were downloaded | |
| ls -lh src/arcadedb_embedded/jars/ | |
| JAR_COUNT=$(ls -1 src/arcadedb_embedded/jars/*.jar 2>/dev/null | wc -l) | |
| echo "✅ Downloaded $JAR_COUNT JAR files" | |
| - name: Remove excluded JARs | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| JARS_DIR="src/arcadedb_embedded/jars" | |
| EXCLUSIONS_FILE="jar_exclusions.txt" | |
| if [[ -f "$EXCLUSIONS_FILE" ]]; then | |
| echo "🗑️ Removing excluded JARs from jar_exclusions.txt..." | |
| EXCLUSION_COUNT=0 | |
| while IFS= read -r pattern || [[ -n "$pattern" ]]; do | |
| # Skip empty lines and comments | |
| if [[ -n "$pattern" ]] && [[ ! "$pattern" =~ ^# ]]; then | |
| echo " Processing pattern: $pattern" | |
| # Remove matching JARs | |
| for jar in "$JARS_DIR"/$pattern; do | |
| if [[ -f "$jar" ]]; then | |
| rm -f "$jar" | |
| echo " - Removed: $(basename "$jar")" | |
| EXCLUSION_COUNT=$((EXCLUSION_COUNT + 1)) | |
| fi | |
| done | |
| fi | |
| done < "$EXCLUSIONS_FILE" | |
| JAR_COUNT_AFTER=$(ls -1 "$JARS_DIR"/*.jar 2>/dev/null | wc -l) | |
| echo "✅ Removed $EXCLUSION_COUNT JAR(s), $JAR_COUNT_AFTER remaining" | |
| else | |
| echo "⚠️ No jar_exclusions.txt found, skipping exclusions" | |
| fi | |
| - name: Upload filtered JARs as artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: arcadedb-jars | |
| path: bindings/python/src/arcadedb_embedded/jars/*.jar | |
| retention-days: 1 | |
| # Second job: Build and test on each platform | |
| test: | |
| name: Test arcadedb-embedded (${{ matrix.platform }}, Python ${{ matrix.python-version }}) | |
| runs-on: ${{ matrix.runs-on }} | |
| needs: download-jars | |
| env: | |
| BUILD_VERSION: ${{ inputs.build-version }} | |
| strategy: | |
| # Don't cancel other matrix jobs if one fails | |
| fail-fast: false | |
| matrix: | |
| python-version: | |
| - '3.10' | |
| - '3.11' | |
| - '3.12' | |
| - '3.13' | |
| - '3.14' | |
| # Temporarily limit to four platforms (skip macOS x86_64, Windows ARM64) | |
| # platform: ['linux/amd64', 'linux/arm64', 'darwin/amd64', 'darwin/arm64', 'windows/amd64', 'windows/arm64'] | |
| platform: ['linux/amd64', 'linux/arm64', 'darwin/arm64', 'windows/amd64'] | |
| include: | |
| - platform: linux/amd64 | |
| runs-on: ubuntu-24.04 | |
| - platform: linux/arm64 | |
| runs-on: ubuntu-24.04-arm | |
| - platform: darwin/arm64 | |
| runs-on: macos-15 | |
| - platform: windows/amd64 | |
| runs-on: windows-2025 | |
| # - platform: darwin/amd64 | |
| # runs-on: macos-15-intel | |
| # - platform: windows/arm64 | |
| # runs-on: windows-11-arm | |
| # macOS x86_64 and Windows ARM64 temporarily disabled | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Setup UV package manager | |
| shell: bash | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| export PATH="$HOME/.local/bin:$PATH" | |
| uv --version | |
| - name: Download ArcadeDB JARs artifact | |
| # Skip for Linux: Docker build downloads JARs from ArcadeDB image directly | |
| # Only needed for native builds (macOS/Windows) | |
| if: matrix.platform != 'linux/amd64' && matrix.platform != 'linux/arm64' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: arcadedb-jars | |
| path: bindings/python/src/arcadedb_embedded/jars | |
| - name: Set up Java (for native builds on macOS/Windows) | |
| if: matrix.platform != 'linux/amd64' && matrix.platform != 'linux/arm64' | |
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 | |
| with: | |
| distribution: 'corretto' | |
| java-version: '25' | |
| - name: Set up Docker Buildx (Linux only) | |
| if: matrix.platform == 'linux/amd64' || matrix.platform == 'linux/arm64' | |
| uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 | |
| - name: Install Python build dependencies | |
| shell: bash | |
| run: | | |
| uv pip install --system build wheel setuptools | |
| - name: Set UTF-8 encoding (Windows only) | |
| if: matrix.platform == 'windows/amd64' || matrix.platform == 'windows/arm64' | |
| shell: bash | |
| run: | | |
| echo "PYTHONIOENCODING=utf-8" >> $GITHUB_ENV | |
| echo "PYTHONUTF8=1" >> $GITHUB_ENV | |
| # Windows currently enabled, no symlink needed | |
| # - name: Create python3 symlink (Windows only) | |
| # if: matrix.platform == 'windows/amd64' || matrix.platform == 'windows/arm64' | |
| # shell: bash | |
| # run: | | |
| # PYTHON_DIR=$(dirname "$(which python)") | |
| # ln -s "$PYTHON_DIR/python.exe" "$PYTHON_DIR/python3.exe" || true | |
| # python3 --version | |
| - name: Build and test arcadedb-embedded (${{ matrix.platform }}) | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| echo "🔨 Building arcadedb-embedded for ${{ matrix.platform }} with Python ${{ matrix.python-version }}..." | |
| ./scripts/build.sh ${{ matrix.platform }} ${{ matrix.python-version }} | |
| - name: Extract wheel for additional testing | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| mkdir -p test-install | |
| cp dist/*.whl test-install/ | |
| - name: Set up Python for host testing | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| # Note: Java is NOT required for arcadedb-embedded (JRE is bundled) | |
| # This package works without any external Java installation! | |
| - name: Install wheel and test dependencies | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| uv pip install --system test-install/*.whl pytest pytest-cov requests numpy | |
| - name: Run pytest on host | |
| id: pytest | |
| shell: bash | |
| run: | | |
| cd bindings/python | |
| echo "🧪 Testing arcadedb-embedded (with bundled JRE)..." | |
| # Run pytest with JUnit XML output for structured results | |
| set +e # Don't exit on test failures | |
| pytest tests/ -v --tb=short --color=yes --junitxml=test-results.xml | |
| PYTEST_EXIT_CODE=$? | |
| set -e | |
| # Parse JUnit XML for test counts (cross-platform XML parsing) | |
| if [ -f "test-results.xml" ]; then | |
| # Extract counts from testsuite element | |
| TOTAL=$(grep -oE 'tests="[0-9]+"' test-results.xml | grep -oE '[0-9]+' | head -1) | |
| FAILED=$(grep -oE 'failures="[0-9]+"' test-results.xml | grep -oE '[0-9]+' | head -1) | |
| SKIPPED=$(grep -oE 'skipped="[0-9]+"' test-results.xml | grep -oE '[0-9]+' | head -1) | |
| ERRORS=$(grep -oE 'errors="[0-9]+"' test-results.xml | grep -oE '[0-9]+' | head -1) | |
| # Default to 0 if not found | |
| TOTAL=${TOTAL:-0} | |
| FAILED=${FAILED:-0} | |
| SKIPPED=${SKIPPED:-0} | |
| ERRORS=${ERRORS:-0} | |
| # Calculate passed tests | |
| PASSED=$((TOTAL - FAILED - SKIPPED - ERRORS)) | |
| echo "passed=$PASSED" >> $GITHUB_OUTPUT | |
| echo "skipped=$SKIPPED" >> $GITHUB_OUTPUT | |
| echo "failed=$FAILED" >> $GITHUB_OUTPUT | |
| echo "✅ Test results: $PASSED passed, $SKIPPED skipped, $FAILED failed" | |
| # Force failure if there are failures or errors | |
| if [ "$FAILED" -gt 0 ] || [ "$ERRORS" -gt 0 ]; then | |
| echo "❌ Tests failed (failures=$FAILED, errors=$ERRORS)" | |
| exit 1 | |
| fi | |
| else | |
| echo "⚠️ test-results.xml not found" | |
| exit 1 | |
| fi | |
| # Exit with pytest's exit code | |
| exit $PYTEST_EXIT_CODE | |
| - name: Generate test summary | |
| if: always() | |
| shell: python | |
| run: | | |
| import os | |
| import zipfile | |
| import glob | |
| from pathlib import Path | |
| summary_file = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null') | |
| with open(summary_file, 'a', encoding='utf-8') as f: | |
| f.write("## 🧪 Test Results: arcadedb-embedded (${{ matrix.platform }})\n") | |
| f.write("\n") | |
| # Check test status | |
| if "${{ steps.pytest.outcome }}" == "success": | |
| f.write("✅ **Status**: PASSED\n") | |
| else: | |
| f.write("❌ **Status**: FAILED\n") | |
| f.write("\n") | |
| f.write("| Metric | Count |\n") | |
| f.write("|--------|-------|\n") | |
| # Show test results | |
| f.write("| ✅ Passed | ${{ steps.pytest.outputs.passed || 'N/A' }} |\n") | |
| f.write("| ⏭️ Skipped | ${{ steps.pytest.outputs.skipped || 'N/A' }} |\n") | |
| f.write("| ❌ Failed | ${{ steps.pytest.outputs.failed || 'N/A' }} |\n") | |
| f.write("\n") | |
| f.write("### 📦 Package Info:\n") | |
| f.write("\n") | |
| # Get wheel size and analyze contents | |
| wheel_files = glob.glob("bindings/python/dist/*.whl") | |
| if wheel_files: | |
| wheel_file = wheel_files[0] | |
| # Get wheel size | |
| wheel_size_bytes = os.path.getsize(wheel_file) | |
| wheel_size_mb = wheel_size_bytes / 1024 / 1024 | |
| # Analyze wheel contents | |
| jre_size_mb = "N/A" | |
| jar_size_mb = "N/A" | |
| installed_size_mb = "N/A" | |
| try: | |
| # Extract and analyze (cross-platform) | |
| import tempfile | |
| import shutil | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| with zipfile.ZipFile(wheel_file, 'r') as whl: | |
| whl.extractall(temp_dir) | |
| # Calculate JRE size | |
| jre_dir = None | |
| for root, dirs, files in os.walk(temp_dir): | |
| if 'jre' in dirs: | |
| jre_dir = os.path.join(root, 'jre') | |
| break | |
| if jre_dir and os.path.isdir(jre_dir): | |
| jre_size_kb = 0 | |
| for root, dirs, files in os.walk(jre_dir): | |
| for file in files: | |
| jre_size_kb += os.path.getsize(os.path.join(root, file)) | |
| jre_size_mb = jre_size_kb / 1024 / 1024 | |
| # Calculate JAR size | |
| jar_files = [] | |
| for root, dirs, files in os.walk(temp_dir): | |
| for file in files: | |
| if file.endswith('.jar'): | |
| jar_files.append(os.path.join(root, file)) | |
| if jar_files: | |
| jar_size_kb = sum(os.path.getsize(f) for f in jar_files) / 1024 | |
| jar_size_mb = jar_size_kb / 1024 | |
| # Calculate total size | |
| total_size_kb = 0 | |
| for root, dirs, files in os.walk(temp_dir): | |
| for file in files: | |
| total_size_kb += os.path.getsize(os.path.join(root, file)) | |
| installed_size_mb = total_size_kb / 1024 / 1024 | |
| except Exception as e: | |
| print(f"Warning: Could not analyze wheel contents: {e}") | |
| f.write(f"- **Wheel Size**: {wheel_size_mb:.1f}M (compressed)\n") | |
| if isinstance(jre_size_mb, str): | |
| f.write(f"- **JRE Size**: {jre_size_mb} (uncompressed)\n") | |
| else: | |
| f.write(f"- **JRE Size**: {jre_size_mb:.1f}M (uncompressed)\n") | |
| if isinstance(jar_size_mb, str): | |
| f.write(f"- **JARs Size**: {jar_size_mb} (uncompressed)\n") | |
| else: | |
| f.write(f"- **JARs Size**: {jar_size_mb:.1f}M (uncompressed)\n") | |
| if isinstance(installed_size_mb, str): | |
| f.write(f"- **Installed Size**: {installed_size_mb} (total uncompressed)\n") | |
| else: | |
| f.write(f"- **Installed Size**: ~{installed_size_mb:.0f}M (total uncompressed)\n") | |
| f.write("- **Platform**: ${{ matrix.platform }}\n") | |
| f.write("- **Features**: All ArcadeDB features (some JARs excluded, see jar_exclusions.txt)\n") | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: test-results-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || matrix.platform == 'linux/arm64' && 'linux-arm64' || matrix.platform == 'darwin/amd64' && 'darwin-amd64' || matrix.platform == 'darwin/arm64' && 'darwin-arm64' || matrix.platform == 'windows/amd64' && 'windows-amd64' || 'windows-arm64' }}-py${{ matrix.python-version }} | |
| path: | | |
| bindings/python/pytest-output.txt | |
| bindings/python/.coverage | |
| retention-days: 7 | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wheel-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || matrix.platform == 'linux/arm64' && 'linux-arm64' || matrix.platform == 'darwin/amd64' && 'darwin-amd64' || matrix.platform == 'darwin/arm64' && 'darwin-arm64' || matrix.platform == 'windows/amd64' && 'windows-amd64' || 'windows-arm64' }}-py${{ matrix.python-version }} | |
| path: bindings/python/dist/*.whl | |
| retention-days: 7 | |
| - name: Upload wheel artifact for release | |
| if: matrix.python-version == '3.12' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wheel-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || matrix.platform == 'linux/arm64' && 'linux-arm64' || matrix.platform == 'darwin/amd64' && 'darwin-amd64' || matrix.platform == 'darwin/arm64' && 'darwin-arm64' || matrix.platform == 'windows/amd64' && 'windows-amd64' || 'windows-arm64' }}-test | |
| path: bindings/python/dist/*.whl | |
| retention-days: 7 | |
| # Summary job that checks all platforms | |
| test-summary: | |
| name: Test Summary | |
| needs: test | |
| runs-on: ubuntu-24.04 | |
| if: always() | |
| steps: | |
| - name: Check test results | |
| run: | | |
| echo "## 🎯 Overall Test Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.test.result }}" = "success" ]; then | |
| echo "✅ **All platforms passed testing!**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "The arcadedb-embedded package has been successfully built and tested." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Package**: arcadedb-embedded" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "ℹ️ **Note**: Some platform/Python combinations are excluded from testing:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Windows ARM64 (no GitHub-hosted runners available)" >> $GITHUB_STEP_SUMMARY | |
| echo "- macOS x86_64 (temporarily disabled)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "❌ **Some platforms failed testing**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Please check the individual test jobs for details." >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi |