Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f61df4a
feat(python): New python provider with inprocess support
thomaspoignant Feb 20, 2026
6c2d65e
move ci to uv
thomaspoignant Feb 20, 2026
0c5b53c
use read and write wasmtime functions
thomaspoignant Feb 21, 2026
4693892
Merge branch 'main' into python-wasm-provider
thomaspoignant Feb 21, 2026
5e5c718
Merge branch 'main' into python-wasm-provider
thomaspoignant Feb 23, 2026
1b10f1e
fix(python-provider): run async flag resolution in thread via asyncio…
thomaspoignant Feb 23, 2026
6efadbe
chore(python-provider): remove unused deps (pylru, websocket-client, …
thomaspoignant Feb 23, 2026
64079ba
fix(python-provider): default unknown flags to trackable in inprocess…
thomaspoignant Feb 23, 2026
cc40e02
feat(python-provider): add WASM store pool for concurrent in-process …
thomaspoignant Feb 23, 2026
5857636
chore(python-provider): default wasm pool size to 10
thomaspoignant Feb 24, 2026
32fe79c
HEAD
thomaspoignant Feb 25, 2026
739531c
fix(python-provider): make event_publisher tests deterministic for CI
thomaspoignant Feb 25, 2026
0891714
docs: update AGENTS.md to reflect current codebase structure
thomaspoignant Feb 25, 2026
0ed46d3
Merge branch 'main' into python-wasm-provider
thomaspoignant Feb 25, 2026
bd06063
test(python-provider): add in-process evaluation tests
thomaspoignant Feb 25, 2026
c5da61d
move mock files
thomaspoignant Feb 25, 2026
b4b5e43
Merge branch 'main' into python-wasm-provider
thomaspoignant Feb 26, 2026
ea0d224
manage all errors
thomaspoignant Mar 10, 2026
65598ec
set as prerelease
thomaspoignant Mar 10, 2026
6936e5e
Merge branch 'main' into python-wasm-provider
thomaspoignant Mar 10, 2026
82fe44e
fix: remove @staticmethod from _raise_for_error_code to fix missing '…
thomaspoignant Mar 10, 2026
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
3 changes: 2 additions & 1 deletion .github/release-please/release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
"extra-files": [
"pyproject.toml",
"README.md"
]
],
"prerelease": true
}
}
}
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 100
submodules: recursive
- name: Get changed files in the docs folder
id: changed-files-specific
uses: marceloprado/has-changed-path@df1b7a3161b8fb9fd8c90403c66a9e66dfde50cb # v1.0.1
Expand All @@ -199,21 +200,21 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.9
- name: Setup Poetry
- name: Install uv
if: steps.changed-files-specific.outputs.changed == 'true'
uses: abatilo/actions-poetry@3765cf608f2d4a72178a9fc5b918668e542b89b1 # v4.0.0
- name: Poetry install
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: uv sync
if: steps.changed-files-specific.outputs.changed == 'true'
working-directory: ./openfeature/providers/python-provider
run: poetry install
run: uv sync
- name: lint black
if: steps.changed-files-specific.outputs.changed == 'true'
working-directory: ./openfeature/providers/python-provider
run: poetry run black . --check
run: uv run black . --check
- name: Pytest
if: steps.changed-files-specific.outputs.changed == 'true'
working-directory: ./openfeature/providers/python-provider
run: poetry run pytest
run: uv run pytest
GraddleWrapperValidation:
runs-on: ubuntu-latest
permissions:
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/release-python-provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build and publish to PyPi
uses: JRubics/poetry-publish@4b3306307f536bbfcb559603629b3b4f6aef5ab8 # v2.1
- name: Install uv
uses: astral-sh/setup-uv@bd01f18f5d15746b30239de8373e6f36c5be2f19 # v6.3.0
with:
package_directory: ./openfeature/providers/python-provider
pypi_token: ${{ secrets.pypi_token }}
version: "latest"
- name: Build package
working-directory: ./openfeature/providers/python-provider
run: uv build
- name: Publish to PyPi
working-directory: ./openfeature/providers/python-provider
run: uv publish --token ${{ secrets.pypi_token }}
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "openfeature/providers/python-provider/wasm-releases"]
path = openfeature/providers/python-provider/wasm-releases
url = https://github.com/go-feature-flag/wasm-releases.git
70 changes: 39 additions & 31 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ make help # Show all available commands

**Key Features:**
- Feature flag implementation with OpenFeature standard support
- Multiple storage backends (S3, HTTP, Kubernetes, MongoDB, Redis, etc.)
- Multiple storage backends (S3, HTTP, Kubernetes, MongoDB, Redis, GCS, Azure, PostgreSQL, etc.)
- Complex rollout strategies (A/B testing, progressive, scheduled)
- Data export and notification systems
- Data export, notification, and tracking systems

## 🏗️ Architecture

Expand All @@ -36,28 +36,37 @@ OpenFeature SDKs → Relay Proxy (cmd/relayproxy/) → GO Module (ffclient)
**Data Flow:** Retrievers fetch configs → Cache stores flags → Change detection triggers Notifiers → Evaluation generates Events → Exporters send data

**Key Components:**
- **`ffclient`**: Core Go module for direct integration
- **`ffclient` (root package)**: Core Go module for direct integration (root `.go` files are `package ffclient`)
- **`cmd/relayproxy/`**: HTTP API server (uses Echo + Zap logging)
- **`retriever/`**: Flag configuration sources (file, HTTP, S3, K8s, MongoDB, Redis, GitHub, GitLab, Bitbucket, PostgreSQL, Azure)
- **`exporter/`**: Data export destinations (S3, File, Kafka, Kinesis, Webhook, GCS, Pub/Sub, SQS, Azure)
- **`notifier/`**: Change notifications (Slack, Webhook, Discord, Teams, Logs)
- **`modules/core`**: Core logic modules used by OpenFeature providers and WASM
- **`retriever/`**: Flag configuration sources (File, HTTP, S3, K8s, MongoDB, Redis, GitHub, GitLab, Bitbucket, PostgreSQL, Azure Blob Storage, GCS)
- **`exporter/`**: Data export destinations (S3, File, Kafka, Kinesis, Webhook, GCS, Pub/Sub, SQS, Azure, Logs, OpenTelemetry)
- **`notifier/`**: Change notifications (Slack, Webhook, Discord, Microsoft Teams, Logs)
- **`modules/core`**: Core logic module used by OpenFeature providers and WASM

## 📁 Directory Structure

**Root Level:**
- `ffclient/`: Core client package
- `cmd/`: Applications (relayproxy, cli, lint, editor, wasm)
- Root `.go` files: Core `ffclient` package (`variation.go`, `feature_flag.go`, `config.go`, `tracking.go`, `variation_all_flags.go`, `config_exporter.go`)
- `cmd/`: Applications (relayproxy, cli, lint, editor, jsonschema-generator, wasm)
- `retriever/`, `exporter/`, `notifier/`: Integration packages
- `modules/`: Separate Go modules (core, evaluation)
- `modules/`: Separate Go modules (`core` only)
- `internal/`: Internal packages (cache, flagstate, notification, signer)
- `openfeature/providers/`: Some providers (Kotlin, Python) - most are in OpenFeature contrib repos
- `ffcontext/`: Evaluation context package
- `ffuser/`: User context (legacy)
- `model/`: DTO models
- `cmdhelpers/`: Shared CLI helpers (errors, retriever config)
- `utils/`: Utilities (fflog, string helpers, constants)
- `openfeature/providers/`: In-repo providers (Kotlin, Python, PHP, Ruby, Swift)
- `openfeature/provider_tests/`: Integration tests for providers (Go, JS, Java, .NET)
- `testutils/`, `testdata/`, `examples/`, `website/`

**Key Files:**
- `variation.go`: Flag evaluation methods
- `feature_flag.go`: Core logic
- `variation_all_flags.go`: Bulk evaluation of all flags
- `feature_flag.go`: Core logic and initialization
- `config.go`: Configuration structure
- `config_exporter.go`: Exporter configuration
- `tracking.go`: Tracking/experimentation support
- `Makefile`: Primary interface (use `make help`)

## 🔑 Key Concepts
Expand All @@ -72,7 +81,7 @@ OpenFeature SDKs → Relay Proxy (cmd/relayproxy/) → GO Module (ffclient)

**Interfaces:**
- `Retriever`: `Retrieve(ctx context.Context) ([]byte, error)`
- `Exporter`: `Export(ctx context.Context, events []FeatureEvent) error`, `IsBulk() bool`
- `Exporter`: `Export(context.Context, *fflog.FFLogger, []ExportableEvent) error` + `IsBulk() bool`
- `Notifier`: `Notify(cache DiffCache) error`

## 🛠️ Common Tasks
Expand All @@ -86,11 +95,11 @@ OpenFeature SDKs → Relay Proxy (cmd/relayproxy/) → GO Module (ffclient)
6. Update docs

**OpenFeature Providers:**
- Most providers in OpenFeature contrib repos (Go, JS, Java, .NET, Ruby, Swift, PHP)
- Some in this repo: Kotlin (`kotlin-provider/`), Python (`python-provider/`)
- Providers use `modules/core` for evaluation logic
- Most providers in OpenFeature contrib repos (Go, JS, Java, .NET)
- In this repo: Kotlin (`kotlin-provider/`), Python (`python-provider/`), PHP (`php-provider/`), Ruby (`ruby-provider/`), Swift (`swift-provider/`)
- Providers use `modules/core` for evaluation logic

**Modules (`modules/core`:**
**Modules (`modules/core`):**
- Core logic separated for reuse by OpenFeature providers and WASM module
- `modules/core`: Flag structures, context, models, utilities
- Allows independent versioning and smaller dependency trees
Expand All @@ -117,7 +126,7 @@ func TestFunction(t *testing.T) {
}
```

**Commands:** `make test`, `make coverage`, `make bench`
**Commands:** `make test`, `make coverage`, `make bench`, `make provider-tests`
**Coverage:** Aim for 90%+, use `testify/assert`, mock external deps

## 📝 Code Patterns
Expand Down Expand Up @@ -155,11 +164,11 @@ pre-commit install # Install pre-commit hooks
```

**Common Commands:**
- **Build:** `make build`, `make build-relayproxy`, `make build-cli`, `make build-wasm`
- **Dev:** `make watch-relayproxy`, `make watch-doc`
- **Test:** `make test`, `make coverage`, `make bench`
- **Build:** `make build`, `make build-relayproxy`, `make build-cli`, `make build-wasm`, `make build-wasi`, `make build-editor-api`, `make build-jsonschema-generator`, `make build-modules`
- **Dev:** `make watch-relayproxy`, `make watch-doc`, `make serve-doc`
- **Test:** `make test`, `make coverage`, `make bench`, `make provider-tests`
- **Quality:** `make lint`, `make tidy`, `make vendor`
- **Utils:** `make clean`, `make swagger`, `make generate-helm-docs`
- **Utils:** `make clean`, `make swagger`, `make generate-helm-docs`, `make bump-helm-chart-version`, `make bump-wasm-contrib`

**Code Quality:**
- Use `make lint` (golangci-lint)
Expand All @@ -168,9 +177,9 @@ pre-commit install # Install pre-commit hooks

## 🔍 Code Navigation

**Flag Evaluation:** `variation.go` → `feature_flag.go` → `internal/cache/` → `internal/flagstate/`
**API Endpoints:** `cmd/relayproxy/api/routes_*.go` → `controller/` → `model/`
**Configuration:** `config.go` (ffclient), `cmd/relayproxy/config/config.go` (relay proxy)
**Flag Evaluation:** `variation.go` → `feature_flag.go` → `internal/cache/` → `internal/flagstate/`
**API Endpoints:** `cmd/relayproxy/api/routes_*.go` → `controller/` → `model/`
**Configuration:** `config.go` (ffclient), `cmd/relayproxy/config/config.go` (relay proxy)
**Flag Format:** `.schema/flag-schema.json`, `testdata/flag-config.*`, https://gofeatureflag.org/docs

## 🔗 Important Files
Expand All @@ -185,11 +194,10 @@ pre-commit install # Install pre-commit hooks

**Key Libraries:** Echo (HTTP), Koanf (config), OpenTelemetry (observability), Prometheus (metrics), Testcontainers (integration tests)

**Monorepo Modules:**
- Main module (root): Core library
**Monorepo Modules (Go workspace):**
- Main module (root): Core `ffclient` library
- `modules/core`: Core data structures (used by providers/WASM/relayproxy)
- `cmd/wasm`: WebAssembly evaluation
- `openfeature/providers/`: Some providers (most in contrib repos)
- `cmd/wasm`: WebAssembly/WASI evaluation

## 📚 Resources

Expand All @@ -200,8 +208,8 @@ pre-commit install # Install pre-commit hooks

## 🎯 Quick Reference

**Entry Points:** `ffclient.Init()`, `cmd/relayproxy/main.go`, `cmd/cli/main.go`
**Interfaces:** `Retriever`, `Exporter`, `Notifier`, `Cache`
**Entry Points:** `ffclient.Init()`, `cmd/relayproxy/main.go`, `cmd/cli/main.go`
**Interfaces:** `Retriever`, `Exporter`, `Notifier`, `Cache`
**Config:** YAML/JSON/TOML flags, `ffclient.Config`, `cmd/relayproxy/config/config.go`

## ⚠️ What Agents Must NEVER Do
Expand Down
13 changes: 7 additions & 6 deletions openfeature/providers/python-provider/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,10 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock

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

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
Expand Down Expand Up @@ -158,3 +156,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

./gofeatureflag_python_provider/wasm/_wasi_version.txt
./gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_*.wasi
148 changes: 148 additions & 0 deletions openfeature/providers/python-provider/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# GO Feature Flag Python Provider

OpenFeature Python provider for [GO Feature Flag](https://gofeatureflag.org).

## Project Overview

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.

## Architecture

```
gofeatureflag_python_provider/
├── __init__.py # Package exports
├── provider.py # Main GoFeatureFlagProvider class (AbstractProvider implementation)
├── options.py # GoFeatureFlagOptions configuration class
├── hooks/ # OpenFeature hooks
│ ├── __init__.py
│ ├── data_collector.py # Hook for collecting flag evaluation usage data
│ └── enrich_evaluation_context.py # Hook that adds gofeatureflag metadata to context before evaluation
├── metadata.py # Provider metadata
├── request_data_collector.py # Data models for usage collection
├── request_flag_evaluation.py # Request models for flag evaluation API calls
└── response_flag_evaluation.py # Response models for flag evaluation API calls

tests/
├── test_gofeatureflag_python_provider.py # Main provider tests
├── test_enrich_evaluation_context_hook.py # EnrichEvaluationContextHook tests
├── test_provider_graceful_exit.py # Shutdown/cleanup tests
├── test_websocket_cache_invalidation.py # WebSocket cache invalidation tests
├── mock_responses/ # JSON mock responses for testing
├── config.goff.yaml # Test flag configuration
└── docker-compose.yml # Test infrastructure
```

## Key Components

### GoFeatureFlagProvider (`provider.py`)
- Extends `AbstractProvider` from OpenFeature SDK
- Implements all resolve methods: `resolve_boolean_details`, `resolve_string_details`, `resolve_integer_details`, `resolve_float_details`, `resolve_object_details`
- Uses `generic_go_feature_flag_resolver` for all flag types
- Features:
- LRU cache for flag evaluations (`pylru`)
- WebSocket connection for cache invalidation
- Data collection for usage analytics
Comment thread
thomaspoignant marked this conversation as resolved.

### GoFeatureFlagOptions (`options.py`)
Configuration options:
- `endpoint` (required): URL of the GO Feature Flag relay proxy
- `cache_size`: Max cached flags (default: 10000)
- `data_flush_interval`: Interval to flush usage data in ms (default: 60000)
- `disable_data_collection`: Turn off usage tracking (default: false)
- `reconnect_interval`: WebSocket reconnect interval in seconds (default: 60)
- `disable_cache_invalidation`: Disable WebSocket cache invalidation (default: false)
- `api_key`: API key for authenticated requests
- `exporter_metadata`: Custom metadata for evaluation events
- `debug`: Enable debug logging (default: false)
- `urllib3_pool_manager`: Custom HTTP client

### DataCollectorHook (`hooks/data_collector.py`)
- OpenFeature Hook implementation for collecting usage data
- Tracks flag evaluations via `after()` and `error()` hooks
- Flushes data to `/v1/data/collector` endpoint periodically

### EnrichEvaluationContextHook (`hooks/enrich_evaluation_context.py`)
- Enriches the evaluation context with a `gofeatureflag` attribute (from `exporter_metadata`) before flag resolution
- Used by the relay proxy for analytics or filtering; registered automatically by the provider

## Development

### Prerequisites
- Python 3.9+
- uv (package manager)

### Setup
```bash
# Install dependencies
uv sync

# Run a command in the virtual environment
uv run <command>
```

### Running Tests
```bash
# Run all tests
uv run pytest

# Run specific test file
uv run pytest tests/test_gofeatureflag_python_provider.py

# Run with verbose output
uv run pytest -v
```

### Code Style
```bash
# Format code with black
uv run black gofeatureflag_python_provider tests
```

## Key Patterns

### Pydantic Models
- All data classes extend Pydantic `BaseModel` for validation
- Request/response models use `model_dump_json()` for serialization
- Use `model_validate_json()` for deserialization

### HTTP Communication
- Uses `urllib3.PoolManager` for HTTP requests
- POST to `/v1/feature/{flag_key}/eval` for flag evaluation
- POST to `/v1/data/collector` for usage data
- WebSocket at `/ws/v1/flag/change` for cache invalidation

### Caching Strategy
- LRU cache keyed by `{flag_key}:{evaluation_context_hash()}`
- Cache cleared on WebSocket message (flag config changed)
- Set `cacheable` field in response determines if result is cached

### Error Handling
- `FlagNotFoundError`: Flag doesn't exist (404)
- `InvalidContextError`: Invalid evaluation context (400)
- `TypeMismatchError`: Response type doesn't match expected type
- `GeneralError`: Other errors (500+)

## API Reference

The provider communicates with the GO Feature Flag relay proxy:

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/v1/feature/{flag}/eval` | POST | Evaluate a flag |
| `/v1/data/collector` | POST | Send usage data |
| `/ws/v1/flag/change` | WebSocket | Cache invalidation notifications |

## Dependencies

Core:
- `openfeature-sdk`: OpenFeature Python SDK
- `pydantic`: Data validation
- `urllib3`: HTTP client
- `pylru`: LRU cache implementation
- `websocket-client`: WebSocket support
- `rel`: WebSocket reconnection handling

Dev:
- `pytest`: Testing framework
- `black`: Code formatter
- `pytest-docker`: Docker-based integration tests
Comment thread
thomaspoignant marked this conversation as resolved.
Comment thread
thomaspoignant marked this conversation as resolved.
1 change: 1 addition & 0 deletions openfeature/providers/python-provider/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
Empty file.
Loading
Loading