Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
da2df98
docs(readme): add Node.js WASM example and native binding references
aepfli Dec 26, 2025
f504ad0
chore(testbed): update submodule reference for metadata test tags
aepfli Dec 26, 2025
d239aa0
docs: add Python bindings implementation tracking
aepfli Dec 26, 2025
9a462ec
feat(python): add workspace and basic PyO3 evaluate_logic function
aepfli Dec 26, 2025
8777d09
feat(python): add FlagEvaluator class with state management
aepfli Dec 26, 2025
8009159
docs: update TODO progress for Day 2 completion
aepfli Dec 26, 2025
48ed5e7
test(python): add comprehensive tests and type stubs
aepfli Dec 26, 2025
333f86b
docs: update TODO progress for Day 3 completion
aepfli Dec 26, 2025
3c01c09
ci(python): add wheel building and testing workflows
aepfli Dec 26, 2025
9983310
docs: update TODO progress for Day 4 completion
aepfli Dec 26, 2025
7d8803c
docs(python): add comprehensive documentation and examples
aepfli Dec 26, 2025
0c1773b
docs: update README and CLAUDE.md with Python bindings info
aepfli Dec 26, 2025
b8218c8
docs: mark Python bindings implementation complete
aepfli Dec 26, 2025
224fa57
refactor: remove evaluate_logic from public API
aepfli Dec 26, 2025
861f355
fixup: format
aepfli Dec 26, 2025
198df13
fix(ci): create virtualenv for Python bindings test
aepfli Dec 26, 2025
c742915
fix(python): update PyO3 to 0.22 and fix API compatibility
aepfli Dec 26, 2025
fdbacd1
build(ci): use uv for Python package management
aepfli Dec 26, 2025
755498e
docs: add uv usage instructions for local development
aepfli Dec 26, 2025
5fb034e
build: add uv.lock for reproducible Python builds
aepfli Dec 26, 2025
7ed1ef4
ci: target Python 3.9 for testing
aepfli Dec 26, 2025
e9b7bb9
build: restrict Python support to 3.9-3.13
aepfli Dec 26, 2025
a9cdff7
fix(ci): add explicit interpreter flag for cross-compilation
aepfli Dec 26, 2025
5a1c9bd
build: use Python stable ABI (abi3) to reduce wheel builds from 25 to 5
aepfli Dec 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,34 @@ jobs:
name: flagd_evaluator.wasm
path: target/wasm32-unknown-unknown/release/flagd_evaluator.wasm
retention-days: 30

test-python:
name: Test Python Bindings
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- uses: actions/setup-python@v5
with:
python-version: '3.9'

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true

- name: Install dependencies and build package
run: |
cd python
uv sync --group dev
source .venv/bin/activate
maturin develop

- name: Run Python tests
run: |
cd python
source .venv/bin/activate
pytest tests/ -v
139 changes: 139 additions & 0 deletions .github/workflows/python-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Python Wheels

on:
push:
branches: [main, feat/python]
pull_request:
branches: [main]
release:
types: [published]

permissions:
contents: read

jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-python@v5
with:
python-version: '3.9' # Minimum supported version for abi3

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
manylinux: auto
working-directory: python

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.target }}
path: python/dist

windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-python@v5
with:
python-version: '3.9' # Minimum supported version for abi3
architecture: ${{ matrix.target }}

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
working-directory: python

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.target }}
path: python/dist

macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-python@v5
with:
python-version: '3.9' # Minimum supported version for abi3

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
working-directory: python

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.target }}
path: python/dist

sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
working-directory: python

- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: python/dist

release:
name: Release
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v4
with:
path: dist
pattern: wheels-*
merge-multiple: true

- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing dist/*
107 changes: 106 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ cargo clippy -- -D warnings

```
src/
├── lib.rs # Main entry point, WASM exports (evaluate_logic, update_state, evaluate)
├── lib.rs # Main entry point, WASM exports (update_state, evaluate)
├── evaluation.rs # Core flag evaluation logic, context enrichment ($flagd properties)
├── memory.rs # WASM memory management (alloc/dealloc, pointer packing)
├── storage/ # Thread-local flag state storage
Expand Down Expand Up @@ -302,6 +302,111 @@ See `examples/java/FlagdEvaluatorExample.java` for complete working example.

**Memory Lifecycle**: Host application owns all memory allocation/deallocation decisions. WASM module only allocates result memory internally.

## Python Native Bindings

In addition to WASM integration, this project provides **native Python bindings** using PyO3 for better performance and developer experience.

### Structure

```
python/
├── src/
│ └── lib.rs # PyO3 bindings (FlagEvaluator class)
├── tests/ # Python test suite (pytest)
├── examples/ # Usage examples
├── benchmarks/ # Performance benchmarks
├── Cargo.toml # PyO3 dependencies
├── pyproject.toml # Maturin build config
└── README.md # Python-specific documentation
```

### Building Python Bindings

**Recommended: Using uv (faster)**

```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Set up development environment (installs deps and creates venv)
cd python
uv sync --group dev
source .venv/bin/activate

# Build and install locally
maturin develop

# Run tests
pytest tests/ -v

# Build wheels for distribution
maturin build --release
```

**Alternative: Using pip**

```bash
# Install maturin
pip install maturin

# Build and install locally
cd python
maturin develop

# Run tests
pytest tests/ -v

# Build wheels for distribution
maturin build --release
```

### Key Differences from WASM

**API Design**: Pythonic dictionaries instead of JSON strings:
```python
# PyO3 API (native)
evaluator = FlagEvaluator()
evaluator.update_state({"flags": {"myFlag": {...}}})
result = evaluator.evaluate("myFlag", {})

# vs WASM API (for comparison)
config_json = json.dumps({"flags": {"myFlag": {...}}})
update_state_wasm(config_json)
result_json = evaluate_wasm("myFlag", "{}")
result = json.loads(result_json)
```

**State Management**: Python class with internal state instead of thread-local storage:
```python
evaluator = FlagEvaluator() # Instance-based state
evaluator.update_state(config)
result = evaluator.evaluate_bool("myFlag", {}, False)
```

**Error Handling**: Native Python exceptions instead of JSON error responses:
```python
try:
result = evaluator.evaluate_bool("nonexistent", {}, False)
except KeyError as e:
print(f"Flag not found: {e}")
```

### Performance

Native bindings provide **5-10x better performance** than WASM:
- No WASM instantiation overhead
- Direct memory sharing (no serialization)
- Native Python exceptions (no JSON parsing)

### CI/CD

Python wheels are built automatically for:
- **Linux**: x86_64, aarch64 (manylinux)
- **macOS**: x86_64, aarch64 (Apple Silicon)
- **Windows**: x64

See `.github/workflows/python-wheels.yml` for the build configuration.

## Related Documentation

- **Flagd Provider Specification**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[workspace]
members = [".", "python"]

[package]
name = "flagd-evaluator"
version = "0.1.0"
Expand Down
Loading
Loading