PYTHON-5683: Spike: Investigate using Rust for Extension Modules #2
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: Rust Extension Tests | |
| on: | |
| push: | |
| branches: ["master", "v**"] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: rust-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| defaults: | |
| run: | |
| shell: bash -eux {0} | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-and-test: | |
| name: Rust Extension - ${{ matrix.os }} - Python ${{ matrix.python-version }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] | |
| exclude: | |
| # Reduce matrix size - test all Python versions on Linux, subset on others | |
| - os: macos-latest | |
| python-version: "3.10" | |
| - os: macos-latest | |
| python-version: "3.11" | |
| - os: windows-latest | |
| python-version: "3.10" | |
| - os: windows-latest | |
| python-version: "3.11" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: 'pip' | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Rust dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| bson/_rbson/target/ | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('bson/_rbson/Cargo.lock') }} | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install maturin pytest | |
| - name: Build C extension | |
| run: | | |
| pip install setuptools | |
| python _setup.py build_ext -i | |
| - name: Build Rust extension | |
| run: | | |
| cd bson/_rbson | |
| maturin develop --release | |
| - name: Verify both extensions are available | |
| run: | | |
| python -c " | |
| import bson | |
| print(f'C extension available: {bson.has_c()}') | |
| print(f'Rust extension available: {bson.has_rust()}') | |
| assert bson.has_c(), 'C extension should be available' | |
| assert bson.has_rust(), 'Rust extension should be available' | |
| " | |
| - name: Test with C extension (default) | |
| run: | | |
| python -c " | |
| import bson | |
| assert bson.get_bson_implementation() == 'c', 'Should default to C' | |
| data = {'test': 'c_extension', 'value': 42} | |
| encoded = bson.encode(data) | |
| decoded = bson.decode(encoded) | |
| assert decoded == data, 'C extension encode/decode failed' | |
| print('✓ C extension works') | |
| " | |
| - name: Test with Rust extension | |
| env: | |
| PYMONGO_USE_RUST: "1" | |
| run: | | |
| python -c " | |
| import bson | |
| assert bson.get_bson_implementation() == 'rust', 'Should use Rust' | |
| data = {'test': 'rust_extension', 'value': 99, 'nested': {'key': 'value'}} | |
| encoded = bson.encode(data) | |
| decoded = bson.decode(encoded) | |
| assert decoded == data, 'Rust extension encode/decode failed' | |
| print('✓ Rust extension works') | |
| " | |
| - name: Test cross-compatibility (C → Rust) | |
| run: | | |
| python -c " | |
| import os | |
| import sys | |
| # Encode with C | |
| import bson as bson_c | |
| assert bson_c.get_bson_implementation() == 'c' | |
| data = {'cross': 'compatibility', 'test': True} | |
| c_encoded = bson_c.encode(data) | |
| # Decode with Rust | |
| os.environ['PYMONGO_USE_RUST'] = '1' | |
| for key in list(sys.modules.keys()): | |
| if key.startswith('bson'): | |
| del sys.modules[key] | |
| import bson as bson_rust | |
| assert bson_rust.get_bson_implementation() == 'rust' | |
| rust_decoded = bson_rust.decode(c_encoded) | |
| assert rust_decoded == data, 'Cross-compatibility failed' | |
| print('✓ C → Rust cross-compatibility works') | |
| " | |
| - name: Run performance benchmark | |
| run: | | |
| python test/performance/benchmark_bson.py | |
| - name: Test Rust extension limitations | |
| env: | |
| PYMONGO_USE_RUST: "1" | |
| run: | | |
| python -c " | |
| import bson | |
| from bson import ObjectId | |
| from datetime import datetime | |
| # Test that ObjectId fails gracefully | |
| try: | |
| data = {'_id': ObjectId()} | |
| bson.encode(data) | |
| assert False, 'Should have raised TypeError for ObjectId' | |
| except TypeError as e: | |
| assert 'ObjectId' in str(e) | |
| print('✓ ObjectId limitation detected correctly') | |
| # Test that DateTime fails gracefully | |
| try: | |
| data = {'timestamp': datetime.now()} | |
| bson.encode(data) | |
| assert False, 'Should have raised TypeError for datetime' | |
| except TypeError as e: | |
| assert 'datetime' in str(e) | |
| print('✓ DateTime limitation detected correctly') | |
| " |