Skip to content

Commit 5a902c8

Browse files
feat(python): New python provider with inprocess support
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
1 parent ddc52d3 commit 5a902c8

57 files changed

Lines changed: 5251 additions & 1818 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release-python-provider.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16-
- name: Build and publish to PyPi
17-
uses: JRubics/poetry-publish@4b3306307f536bbfcb559603629b3b4f6aef5ab8 # v2.1
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@bd01f18f5d15746b30239de8373e6f36c5be2f19 # v6.3.0
1818
with:
19-
package_directory: ./openfeature/providers/python-provider
20-
pypi_token: ${{ secrets.pypi_token }}
19+
version: "latest"
20+
- name: Build package
21+
working-directory: ./openfeature/providers/python-provider
22+
run: uv build
23+
- name: Publish to PyPi
24+
working-directory: ./openfeature/providers/python-provider
25+
run: uv publish --token ${{ secrets.pypi_token }}

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "openfeature/providers/python-provider/wasm-releases"]
2+
path = openfeature/providers/python-provider/wasm-releases
3+
url = git@github.com:go-feature-flag/wasm-releases.git

openfeature/providers/python-provider/.gitignore

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,10 @@ ipython_config.py
9494
# install all needed dependencies.
9595
#Pipfile.lock
9696

97-
# poetry
98-
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99-
# This is especially recommended for binary packages to ensure reproducibility, and is more
100-
# commonly ignored for libraries.
101-
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102-
#poetry.lock
97+
# uv
98+
# uv.lock is recommended to be included in version control for reproducibility.
99+
# https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile
100+
#uv.lock
103101

104102
# pdm
105103
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
@@ -158,3 +156,6 @@ cython_debug/
158156
# and can be added to the global gitignore or merged into this file. For a more nuclear
159157
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160158
.idea/
159+
160+
./gofeatureflag_python_provider/wasm/_wasi_version.txt
161+
./gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_*.wasi
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# GO Feature Flag Python Provider
2+
3+
OpenFeature Python provider for [GO Feature Flag](https://gofeatureflag.org).
4+
5+
## Project Overview
6+
7+
This is a Python package that implements the OpenFeature provider interface to connect to a GO Feature Flag relay proxy. It enables Python applications to evaluate feature flags using the OpenFeature SDK.
8+
9+
## Architecture
10+
11+
```
12+
gofeatureflag_python_provider/
13+
├── __init__.py # Package exports
14+
├── provider.py # Main GoFeatureFlagProvider class (AbstractProvider implementation)
15+
├── options.py # GoFeatureFlagOptions configuration class
16+
├── hooks/ # OpenFeature hooks
17+
│ ├── __init__.py
18+
│ ├── data_collector.py # Hook for collecting flag evaluation usage data
19+
│ └── enrich_evaluation_context.py # Hook that adds gofeatureflag metadata to context before evaluation
20+
├── metadata.py # Provider metadata
21+
├── request_data_collector.py # Data models for usage collection
22+
├── request_flag_evaluation.py # Request models for flag evaluation API calls
23+
└── response_flag_evaluation.py # Response models for flag evaluation API calls
24+
25+
tests/
26+
├── test_gofeatureflag_python_provider.py # Main provider tests
27+
├── test_enrich_evaluation_context_hook.py # EnrichEvaluationContextHook tests
28+
├── test_provider_graceful_exit.py # Shutdown/cleanup tests
29+
├── test_websocket_cache_invalidation.py # WebSocket cache invalidation tests
30+
├── mock_responses/ # JSON mock responses for testing
31+
├── config.goff.yaml # Test flag configuration
32+
└── docker-compose.yml # Test infrastructure
33+
```
34+
35+
## Key Components
36+
37+
### GoFeatureFlagProvider (`provider.py`)
38+
- Extends `AbstractProvider` from OpenFeature SDK
39+
- Implements all resolve methods: `resolve_boolean_details`, `resolve_string_details`, `resolve_integer_details`, `resolve_float_details`, `resolve_object_details`
40+
- Uses `generic_go_feature_flag_resolver` for all flag types
41+
- Features:
42+
- LRU cache for flag evaluations (`pylru`)
43+
- WebSocket connection for cache invalidation
44+
- Data collection for usage analytics
45+
46+
### GoFeatureFlagOptions (`options.py`)
47+
Configuration options:
48+
- `endpoint` (required): URL of the GO Feature Flag relay proxy
49+
- `cache_size`: Max cached flags (default: 10000)
50+
- `data_flush_interval`: Interval to flush usage data in ms (default: 60000)
51+
- `disable_data_collection`: Turn off usage tracking (default: false)
52+
- `reconnect_interval`: WebSocket reconnect interval in seconds (default: 60)
53+
- `disable_cache_invalidation`: Disable WebSocket cache invalidation (default: false)
54+
- `api_key`: API key for authenticated requests
55+
- `exporter_metadata`: Custom metadata for evaluation events
56+
- `debug`: Enable debug logging (default: false)
57+
- `urllib3_pool_manager`: Custom HTTP client
58+
59+
### DataCollectorHook (`hooks/data_collector.py`)
60+
- OpenFeature Hook implementation for collecting usage data
61+
- Tracks flag evaluations via `after()` and `error()` hooks
62+
- Flushes data to `/v1/data/collector` endpoint periodically
63+
64+
### EnrichEvaluationContextHook (`hooks/enrich_evaluation_context.py`)
65+
- Enriches the evaluation context with a `gofeatureflag` attribute (from `exporter_metadata`) before flag resolution
66+
- Used by the relay proxy for analytics or filtering; registered automatically by the provider
67+
68+
## Development
69+
70+
### Prerequisites
71+
- Python 3.9+
72+
- uv (package manager)
73+
74+
### Setup
75+
```bash
76+
# Install dependencies
77+
uv sync
78+
79+
# Run a command in the virtual environment
80+
uv run <command>
81+
```
82+
83+
### Running Tests
84+
```bash
85+
# Run all tests
86+
uv run pytest
87+
88+
# Run specific test file
89+
uv run pytest tests/test_gofeatureflag_python_provider.py
90+
91+
# Run with verbose output
92+
uv run pytest -v
93+
```
94+
95+
### Code Style
96+
```bash
97+
# Format code with black
98+
uv run black gofeatureflag_python_provider tests
99+
```
100+
101+
## Key Patterns
102+
103+
### Pydantic Models
104+
- All data classes extend Pydantic `BaseModel` for validation
105+
- Request/response models use `model_dump_json()` for serialization
106+
- Use `model_validate_json()` for deserialization
107+
108+
### HTTP Communication
109+
- Uses `urllib3.PoolManager` for HTTP requests
110+
- POST to `/v1/feature/{flag_key}/eval` for flag evaluation
111+
- POST to `/v1/data/collector` for usage data
112+
- WebSocket at `/ws/v1/flag/change` for cache invalidation
113+
114+
### Caching Strategy
115+
- LRU cache keyed by `{flag_key}:{evaluation_context_hash()}`
116+
- Cache cleared on WebSocket message (flag config changed)
117+
- Set `cacheable` field in response determines if result is cached
118+
119+
### Error Handling
120+
- `FlagNotFoundError`: Flag doesn't exist (404)
121+
- `InvalidContextError`: Invalid evaluation context (400)
122+
- `TypeMismatchError`: Response type doesn't match expected type
123+
- `GeneralError`: Other errors (500+)
124+
125+
## API Reference
126+
127+
The provider communicates with the GO Feature Flag relay proxy:
128+
129+
| Endpoint | Method | Purpose |
130+
|----------|--------|---------|
131+
| `/v1/feature/{flag}/eval` | POST | Evaluate a flag |
132+
| `/v1/data/collector` | POST | Send usage data |
133+
| `/ws/v1/flag/change` | WebSocket | Cache invalidation notifications |
134+
135+
## Dependencies
136+
137+
Core:
138+
- `openfeature-sdk`: OpenFeature Python SDK
139+
- `pydantic`: Data validation
140+
- `urllib3`: HTTP client
141+
- `pylru`: LRU cache implementation
142+
- `websocket-client`: WebSocket support
143+
- `rel`: WebSocket reconnection handling
144+
145+
Dev:
146+
- `pytest`: Testing framework
147+
- `black`: Code formatter
148+
- `pytest-docker`: Docker-based integration tests
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

openfeature/providers/python-provider/clean.py

Whitespace-only changes.

openfeature/providers/python-provider/gofeatureflag_python_provider/data_collector_hook.py

Lines changed: 0 additions & 140 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
Evaluator implementations for GO Feature Flag provider.
3+
4+
Selects remote (relay proxy) or inprocess (local/WASM) evaluation based on options.
5+
"""
6+
7+
from gofeatureflag_python_provider.evaluator.abstract_evaluator import AbstractEvaluator
8+
from gofeatureflag_python_provider.evaluator.inprocess_evaluator import (
9+
InProcessEvaluator,
10+
)
11+
from gofeatureflag_python_provider.evaluator.remote_evaluator import RemoteEvaluator
12+
13+
__all__ = [
14+
"AbstractEvaluator",
15+
"InProcessEvaluator",
16+
"RemoteEvaluator",
17+
]

0 commit comments

Comments
 (0)