Skip to content

Commit 8d2baa3

Browse files
aepfliclaude
andauthored
feat: python native bindings (#49)
## Description <!-- Provide a brief description of your changes --> ## Related Issue <!-- Link to the related issue(s) --> Closes # ## Type of Change <!-- Mark the relevant option with an "x" --> - [ ] `feat`: New feature (minor version bump) - [ ] `fix`: Bug fix (patch version bump) - [ ] `docs`: Documentation only changes - [ ] `chore`: Maintenance tasks, dependency updates - [ ] `refactor`: Code refactoring without functional changes - [ ] `test`: Adding or updating tests - [ ] `ci`: CI/CD changes - [ ] `perf`: Performance improvements - [ ] `build`: Build system changes - [ ] `style`: Code style/formatting changes ## PR Title Format **IMPORTANT**: Since we use squash and merge, your PR title will become the commit message. Please ensure your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/) format: ``` <type>(<optional-scope>): <description> ``` ### Examples: - `feat(operators): add new string comparison operator` - `fix(wasm): correct memory allocation bug` - `docs: update API examples in README` - `chore(deps): update rust dependencies` For breaking changes, use `!` after the type/scope or include `BREAKING CHANGE:` in the PR description: - `feat(api)!: redesign evaluation API` ## Testing <!-- Describe the testing you've performed --> - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] All tests pass (`cargo test`) - [ ] Code is formatted (`cargo fmt`) - [ ] Clippy checks pass (`cargo clippy -- -D warnings`) - [ ] WASM builds successfully (if applicable) ## Breaking Changes <!-- If this introduces breaking changes, describe them here --> - [ ] This PR includes breaking changes - [ ] Documentation has been updated to reflect breaking changes - [ ] Migration guide included (if needed) ## Additional Notes <!-- Any additional information, context, or screenshots --> --------- Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 124017e commit 8d2baa3

18 files changed

Lines changed: 2083 additions & 480 deletions

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,34 @@ jobs:
109109
name: flagd_evaluator.wasm
110110
path: target/wasm32-unknown-unknown/release/flagd_evaluator.wasm
111111
retention-days: 30
112+
113+
test-python:
114+
name: Test Python Bindings
115+
runs-on: ubuntu-latest
116+
steps:
117+
- uses: actions/checkout@v4
118+
119+
- name: Install Rust
120+
uses: dtolnay/rust-toolchain@stable
121+
122+
- uses: actions/setup-python@v5
123+
with:
124+
python-version: '3.9'
125+
126+
- name: Install uv
127+
uses: astral-sh/setup-uv@v4
128+
with:
129+
enable-cache: true
130+
131+
- name: Install dependencies and build package
132+
run: |
133+
cd python
134+
uv sync --group dev
135+
source .venv/bin/activate
136+
maturin develop
137+
138+
- name: Run Python tests
139+
run: |
140+
cd python
141+
source .venv/bin/activate
142+
pytest tests/ -v
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
name: Python Wheels
2+
3+
on:
4+
push:
5+
branches: [main, feat/python]
6+
pull_request:
7+
branches: [main]
8+
release:
9+
types: [published]
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
linux:
16+
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
target: [x86_64, aarch64]
20+
steps:
21+
- uses: actions/checkout@v4
22+
with:
23+
submodules: recursive
24+
25+
- uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.9' # Minimum supported version for abi3
28+
29+
- name: Build wheels
30+
uses: PyO3/maturin-action@v1
31+
with:
32+
target: ${{ matrix.target }}
33+
args: --release --out dist
34+
sccache: 'true'
35+
manylinux: auto
36+
working-directory: python
37+
38+
- name: Upload wheels
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: wheels-linux-${{ matrix.target }}
42+
path: python/dist
43+
44+
windows:
45+
runs-on: windows-latest
46+
strategy:
47+
matrix:
48+
target: [x64]
49+
steps:
50+
- uses: actions/checkout@v4
51+
with:
52+
submodules: recursive
53+
54+
- uses: actions/setup-python@v5
55+
with:
56+
python-version: '3.9' # Minimum supported version for abi3
57+
architecture: ${{ matrix.target }}
58+
59+
- name: Build wheels
60+
uses: PyO3/maturin-action@v1
61+
with:
62+
target: ${{ matrix.target }}
63+
args: --release --out dist
64+
sccache: 'true'
65+
working-directory: python
66+
67+
- name: Upload wheels
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: wheels-windows-${{ matrix.target }}
71+
path: python/dist
72+
73+
macos:
74+
runs-on: macos-latest
75+
strategy:
76+
matrix:
77+
target: [x86_64, aarch64]
78+
steps:
79+
- uses: actions/checkout@v4
80+
with:
81+
submodules: recursive
82+
83+
- uses: actions/setup-python@v5
84+
with:
85+
python-version: '3.9' # Minimum supported version for abi3
86+
87+
- name: Build wheels
88+
uses: PyO3/maturin-action@v1
89+
with:
90+
target: ${{ matrix.target }}
91+
args: --release --out dist
92+
sccache: 'true'
93+
working-directory: python
94+
95+
- name: Upload wheels
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: wheels-macos-${{ matrix.target }}
99+
path: python/dist
100+
101+
sdist:
102+
runs-on: ubuntu-latest
103+
steps:
104+
- uses: actions/checkout@v4
105+
with:
106+
submodules: recursive
107+
108+
- name: Build sdist
109+
uses: PyO3/maturin-action@v1
110+
with:
111+
command: sdist
112+
args: --out dist
113+
working-directory: python
114+
115+
- name: Upload sdist
116+
uses: actions/upload-artifact@v4
117+
with:
118+
name: wheels-sdist
119+
path: python/dist
120+
121+
release:
122+
name: Release
123+
runs-on: ubuntu-latest
124+
if: github.event_name == 'release' && github.event.action == 'published'
125+
needs: [linux, windows, macos, sdist]
126+
steps:
127+
- uses: actions/download-artifact@v4
128+
with:
129+
path: dist
130+
pattern: wheels-*
131+
merge-multiple: true
132+
133+
- name: Publish to PyPI
134+
uses: PyO3/maturin-action@v1
135+
env:
136+
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
137+
with:
138+
command: upload
139+
args: --non-interactive --skip-existing dist/*

CLAUDE.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ cargo clippy -- -D warnings
6868

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

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

305+
## Python Native Bindings
306+
307+
In addition to WASM integration, this project provides **native Python bindings** using PyO3 for better performance and developer experience.
308+
309+
### Structure
310+
311+
```
312+
python/
313+
├── src/
314+
│ └── lib.rs # PyO3 bindings (FlagEvaluator class)
315+
├── tests/ # Python test suite (pytest)
316+
├── examples/ # Usage examples
317+
├── benchmarks/ # Performance benchmarks
318+
├── Cargo.toml # PyO3 dependencies
319+
├── pyproject.toml # Maturin build config
320+
└── README.md # Python-specific documentation
321+
```
322+
323+
### Building Python Bindings
324+
325+
**Recommended: Using uv (faster)**
326+
327+
```bash
328+
# Install uv
329+
curl -LsSf https://astral.sh/uv/install.sh | sh
330+
331+
# Set up development environment (installs deps and creates venv)
332+
cd python
333+
uv sync --group dev
334+
source .venv/bin/activate
335+
336+
# Build and install locally
337+
maturin develop
338+
339+
# Run tests
340+
pytest tests/ -v
341+
342+
# Build wheels for distribution
343+
maturin build --release
344+
```
345+
346+
**Alternative: Using pip**
347+
348+
```bash
349+
# Install maturin
350+
pip install maturin
351+
352+
# Build and install locally
353+
cd python
354+
maturin develop
355+
356+
# Run tests
357+
pytest tests/ -v
358+
359+
# Build wheels for distribution
360+
maturin build --release
361+
```
362+
363+
### Key Differences from WASM
364+
365+
**API Design**: Pythonic dictionaries instead of JSON strings:
366+
```python
367+
# PyO3 API (native)
368+
evaluator = FlagEvaluator()
369+
evaluator.update_state({"flags": {"myFlag": {...}}})
370+
result = evaluator.evaluate("myFlag", {})
371+
372+
# vs WASM API (for comparison)
373+
config_json = json.dumps({"flags": {"myFlag": {...}}})
374+
update_state_wasm(config_json)
375+
result_json = evaluate_wasm("myFlag", "{}")
376+
result = json.loads(result_json)
377+
```
378+
379+
**State Management**: Python class with internal state instead of thread-local storage:
380+
```python
381+
evaluator = FlagEvaluator() # Instance-based state
382+
evaluator.update_state(config)
383+
result = evaluator.evaluate_bool("myFlag", {}, False)
384+
```
385+
386+
**Error Handling**: Native Python exceptions instead of JSON error responses:
387+
```python
388+
try:
389+
result = evaluator.evaluate_bool("nonexistent", {}, False)
390+
except KeyError as e:
391+
print(f"Flag not found: {e}")
392+
```
393+
394+
### Performance
395+
396+
Native bindings provide **5-10x better performance** than WASM:
397+
- No WASM instantiation overhead
398+
- Direct memory sharing (no serialization)
399+
- Native Python exceptions (no JSON parsing)
400+
401+
### CI/CD
402+
403+
Python wheels are built automatically for:
404+
- **Linux**: x86_64, aarch64 (manylinux)
405+
- **macOS**: x86_64, aarch64 (Apple Silicon)
406+
- **Windows**: x64
407+
408+
See `.github/workflows/python-wheels.yml` for the build configuration.
409+
305410
## Related Documentation
306411

307412
- **Flagd Provider Specification**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[workspace]
2+
members = [".", "python"]
3+
14
[package]
25
name = "flagd-evaluator"
36
version = "0.1.0"

0 commit comments

Comments
 (0)