gofeatureflag-python-provider 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gofeatureflag_python_provider-1.3.0/AGENTS.md +160 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/CHANGELOG.md +30 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/PKG-INFO +60 -16
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/README.md +54 -10
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/__init__.py +18 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/abstract_evaluator.py +1 -1
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/evaluator/inprocess_evaluator.py +668 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/remote_evaluator.py +5 -27
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/evaluator/util.py +27 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/hooks/data_collector.py +126 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/hooks/enrich_evaluation_context.py +11 -1
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/metadata.py +1 -1
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/options.py +137 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/provider.py +61 -17
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/services/__init__.py +1 -1
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/services/api.py +69 -33
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/event_publisher.py +291 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model/__init__.py +27 -0
- {gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider → gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model}/request_data_collector.py +12 -4
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model/request_flag_configuration.py +11 -0
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/services/models.py → gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model/response_flag_configuration.py +1 -9
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/ofrep.py +54 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/utils.py +34 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/__init__.py +25 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/_wasi_version.txt +1 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/__init__.py +21 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/evaluation_trap.py +10 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/function_not_found.py +5 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/invalid_result.py +5 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/not_loaded.py +5 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/pool_timeout.py +10 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/evaluate_wasm.py +263 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_0.2.4.wasi +0 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/__init__.py +13 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/evaluation_response.py +23 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/flag_context.py +12 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/input.py +19 -0
- gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/wasi_runtime.py +69 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/pyproject.toml +21 -24
- gofeatureflag_python_provider-1.3.0/tests/conftest.py +146 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_data_collector_hook.py +185 -9
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_enrich_evaluation_context_hook.py +37 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_evaluate_wasm.py +282 -7
- gofeatureflag_python_provider-1.3.0/tests/test_evaluator_util.py +107 -0
- gofeatureflag_python_provider-1.3.0/tests/test_event_publisher.py +500 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_gofeatureflag_provider.py +59 -9
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_gofeatureflag_provider_inprocess.py +0 -3
- gofeatureflag_python_provider-1.3.0/tests/test_inprocess_evaluator.py +1377 -0
- gofeatureflag_python_provider-1.3.0/tests/test_integration_relayproxy.py +263 -0
- gofeatureflag_python_provider-1.3.0/tests/test_logging.py +110 -0
- gofeatureflag_python_provider-1.3.0/tests/test_options.py +122 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_remote_evaluator_ofrep.py +67 -7
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_services_api.py +206 -17
- gofeatureflag_python_provider-1.3.0/tests/test_utils.py +61 -0
- gofeatureflag_python_provider-1.3.0/tests/test_wasm_trap_diagnosis.py +489 -0
- gofeatureflag_python_provider-1.3.0/tests/wasm_helpers.py +63 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/uv.lock +172 -44
- gofeatureflag_python_provider-1.2.0/AGENTS.md +0 -148
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/__init__.py +0 -1
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/evaluator/inprocess_evaluator.py +0 -327
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/hooks/data_collector.py +0 -83
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/options.py +0 -97
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/response_flag_evaluation.py +0 -16
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/services/event_publisher.py +0 -157
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/__init__.py +0 -15
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/_wasi_version.txt +0 -1
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/evaluate_wasm.py +0 -211
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_0.2.3.wasi +0 -0
- gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/models.py +0 -40
- gofeatureflag_python_provider-1.2.0/tests/config.goff.yaml +0 -176
- gofeatureflag_python_provider-1.2.0/tests/conftest.py +0 -0
- gofeatureflag_python_provider-1.2.0/tests/docker-compose.yml +0 -15
- gofeatureflag_python_provider-1.2.0/tests/test_event_publisher.py +0 -156
- gofeatureflag_python_provider-1.2.0/tests/test_inprocess_evaluator.py +0 -568
- gofeatureflag_python_provider-1.2.0/tests/test_websocket_cache_invalidation.py +0 -121
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/.gitignore +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/CLAUDE.md +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/clean.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/__init__.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/exceptions.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/hooks/__init__.py +0 -0
- {gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider → gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model}/request_flag_evaluation.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/hatch_build.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/.gitignore +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/__init__.py +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-all-types-config-change.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-all-types.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-flag-config.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-scheduled-rollout.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/bool_targeting_match.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/bool_targeting_match_no_error_field.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_bool.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_float.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_int.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_object.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_string.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/double_key.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/flag_not_found.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/integer_key.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/invalid_json_body.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/list_key.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/object_key.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/string_key.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/unknown_reason.json +0 -0
- {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_provider_graceful_exit.py +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
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 package implements the OpenFeature provider interface for GO Feature Flag.
|
|
8
|
+
It supports two evaluation modes, selected with `options.evaluation_type`:
|
|
9
|
+
|
|
10
|
+
- **In-process** _(default)_: the provider polls the relay proxy for the flag
|
|
11
|
+
configuration and evaluates flags locally through the embedded WASM
|
|
12
|
+
evaluation engine (`wasmtime`). No network call per evaluation.
|
|
13
|
+
- **Remote**: every evaluation is delegated to the relay proxy through the
|
|
14
|
+
OpenFeature Remote Evaluation Protocol (OFREP), via the
|
|
15
|
+
`openfeature-provider-ofrep` package.
|
|
16
|
+
|
|
17
|
+
There is **no evaluation-result cache** in either mode (the former `pylru`
|
|
18
|
+
LRU cache and WebSocket invalidation were removed with the in-process
|
|
19
|
+
rewrite). The cache is an optional capability in the provider specification;
|
|
20
|
+
this provider does not implement it.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
gofeatureflag_python_provider/
|
|
26
|
+
├── provider.py # GoFeatureFlagProvider (AbstractProvider impl), hook wiring, provider events
|
|
27
|
+
├── options.py # GoFeatureFlagOptions configuration
|
|
28
|
+
├── metadata.py # Provider metadata ("GO Feature Flag Provider")
|
|
29
|
+
├── exceptions.py # Provider exceptions
|
|
30
|
+
├── utils.py # contextKind helpers etc.
|
|
31
|
+
├── evaluator/
|
|
32
|
+
│ ├── abstract_evaluator.py # AbstractEvaluator interface (sync + async resolves, is_flag_trackable)
|
|
33
|
+
│ ├── inprocess_evaluator.py # Polls flag configuration, evaluates via WASM, remote fallback via OFREP
|
|
34
|
+
│ ├── remote_evaluator.py # Delegates every resolve to the OFREP provider; flags are never trackable
|
|
35
|
+
│ └── util.py
|
|
36
|
+
├── hooks/
|
|
37
|
+
│ ├── data_collector.py # Collects FeatureEvents for local evaluations (after/error)
|
|
38
|
+
│ └── enrich_evaluation_context.py # Adds the gofeatureflag metadata to the context before evaluation
|
|
39
|
+
├── services/
|
|
40
|
+
│ ├── api.py # HTTP client: flag configuration (ETag aware) + data collector
|
|
41
|
+
│ ├── event_publisher.py # Buffers events, bulk-flushes on interval/threshold/shutdown
|
|
42
|
+
│ ├── ofrep.py # Shared OFREP client construction (auth, headers, timeout)
|
|
43
|
+
│ └── model/ # Pydantic request/response models
|
|
44
|
+
└── wasm/
|
|
45
|
+
├── evaluate_wasm.py # WASM evaluation entry point, instance pool, trap handling
|
|
46
|
+
├── wasi_runtime.py # wasmtime runtime setup
|
|
47
|
+
├── errors/ # Trap/pool/loading error types
|
|
48
|
+
├── model/ # WASM input/output models
|
|
49
|
+
└── gofeatureflag-evaluation_<version>.wasi # Bundled engine (pinned in _wasi_version.txt)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Key Components
|
|
53
|
+
|
|
54
|
+
### GoFeatureFlagProvider (`provider.py`)
|
|
55
|
+
- Extends `AbstractProvider`; delegates all `resolve_*_details` (sync and
|
|
56
|
+
async) to the evaluator chosen from `options.evaluation_type`.
|
|
57
|
+
- Registers two provider hooks, in order: `EnrichEvaluationContextHook`, then
|
|
58
|
+
`DataCollectorHook` (the collector must observe the enriched context).
|
|
59
|
+
- Emits provider events (`configuration changed`, `stale`, `ready`) from the
|
|
60
|
+
in-process polling loop.
|
|
61
|
+
|
|
62
|
+
### GoFeatureFlagOptions (`options.py`)
|
|
63
|
+
- `endpoint` (required): URL of the relay proxy
|
|
64
|
+
- `evaluation_type`: `INPROCESS` (default) or `REMOTE`
|
|
65
|
+
- `api_key`: sent as an `X-API-Key` header when set
|
|
66
|
+
- `timeout`: HTTP timeout in ms (default: 10000)
|
|
67
|
+
- `flag_config_poll_interval_seconds`: in-process polling interval (default: 120)
|
|
68
|
+
- `evaluation_flag_list`: restrict fetched flag configuration (in-process only)
|
|
69
|
+
- `wasm_file_path`, `wasm_pool_size`: WASM binary override / instance pool size
|
|
70
|
+
(default pool size: CPU core count)
|
|
71
|
+
- `data_flush_interval` (ms, default 60000), `max_pending_events`
|
|
72
|
+
(default 10000), `disable_data_collection`, `exporter_metadata`,
|
|
73
|
+
`data_collector_base_url`: data collection tuning
|
|
74
|
+
- `custom_headers`, `urllib3_pool_manager`, `log_level`
|
|
75
|
+
|
|
76
|
+
### DataCollectorHook (`hooks/data_collector.py`)
|
|
77
|
+
- Builds a `FeatureEvent` in `after()`/`error()` and enqueues it on the
|
|
78
|
+
`EventPublisher`.
|
|
79
|
+
- Skips the event when data collection is disabled, when the evaluator reports
|
|
80
|
+
the flag as not trackable, or when the result carries the
|
|
81
|
+
`gofeatureflag_evaluated_remotely` metadata (the relay proxy already
|
|
82
|
+
recorded it — emitting would double count).
|
|
83
|
+
- **Remote mode emits no feature events at all**: `RemoteEvaluator.
|
|
84
|
+
is_flag_trackable()` always returns `False` because every remote evaluation
|
|
85
|
+
is already recorded server-side. `source` is always `INPROCESS`.
|
|
86
|
+
|
|
87
|
+
### EventPublisher (`services/event_publisher.py`)
|
|
88
|
+
- Buffers events and bulk-posts them to `/v1/data/collector` on a periodic
|
|
89
|
+
interval, when the buffer reaches `max_pending_events`, and on shutdown.
|
|
90
|
+
- Never posts an empty batch; failed batches are re-queued in order; the
|
|
91
|
+
buffer is capped at twice `max_pending_events` (oldest dropped first).
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
| Endpoint | Method | Purpose |
|
|
96
|
+
|----------|--------|---------|
|
|
97
|
+
| `/v1/flag/configuration` | POST | Fetch flag configuration (in-process; ETag/If-None-Match aware) |
|
|
98
|
+
| `/ofrep/v1/evaluate/flags/{flag_key}` | POST | Remote evaluation via OFREP |
|
|
99
|
+
| `/v1/data/collector` | POST | Bulk usage/evaluation data |
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
### Prerequisites
|
|
104
|
+
- Python 3.9+
|
|
105
|
+
- uv (package manager)
|
|
106
|
+
- Docker (optional — only for the container-backed tests, which skip without it)
|
|
107
|
+
|
|
108
|
+
### Setup and common commands
|
|
109
|
+
```bash
|
|
110
|
+
uv sync # Install dependencies
|
|
111
|
+
uv run pytest # Run all tests
|
|
112
|
+
uv run pytest tests/test_inprocess_evaluator.py # One file
|
|
113
|
+
uv run black gofeatureflag_python_provider tests # Format
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Key Patterns
|
|
117
|
+
|
|
118
|
+
- **Pydantic models** for options and API payloads (`model_dump_json()` /
|
|
119
|
+
`model_validate_json()`).
|
|
120
|
+
- **HTTP**: `urllib3.PoolManager` for flag configuration and data collection;
|
|
121
|
+
the OFREP client (built in `services/ofrep.py`) handles remote evaluation.
|
|
122
|
+
Both paths share auth/header/timeout construction.
|
|
123
|
+
- **WASM**: `wasmtime.Store` is not thread-safe — evaluation uses an instance
|
|
124
|
+
pool; a trapped instance is poisoned and recycled, never reused.
|
|
125
|
+
- **Error handling**: evaluators raise OpenFeature exceptions
|
|
126
|
+
(`FlagNotFoundError`, `TypeMismatchError`, `InvalidContextError`,
|
|
127
|
+
`GeneralError`); the SDK converts them into default-value results.
|
|
128
|
+
|
|
129
|
+
## Tests
|
|
130
|
+
|
|
131
|
+
Table of the main test files (pytest):
|
|
132
|
+
|
|
133
|
+
| File | Covers |
|
|
134
|
+
|------|--------|
|
|
135
|
+
| `test_gofeatureflag_provider.py` | Provider end-to-end (remote mode) |
|
|
136
|
+
| `test_gofeatureflag_provider_inprocess.py` | Provider end-to-end (in-process) |
|
|
137
|
+
| `test_inprocess_evaluator.py` | In-process evaluator, polling, fallback |
|
|
138
|
+
| `test_remote_evaluator_ofrep.py` | Remote evaluator delegation to OFREP |
|
|
139
|
+
| `test_evaluate_wasm.py`, `test_wasm_trap_diagnosis.py` | WASM engine host, trap handling |
|
|
140
|
+
| `test_data_collector_hook.py`, `test_event_publisher.py` | Data collection |
|
|
141
|
+
| `test_options.py` | Options, including removal of the old cache options |
|
|
142
|
+
| `test_services_api.py` | HTTP layer (ETag, status mapping) |
|
|
143
|
+
| `test_integration_relayproxy.py` | Real evaluations against a containerised relay proxy (both modes) |
|
|
144
|
+
|
|
145
|
+
### Container-backed tests
|
|
146
|
+
|
|
147
|
+
Every test file above this one mocks the transport, so the recordings are only
|
|
148
|
+
ever checked against themselves. `test_integration_relayproxy.py` boots a real
|
|
149
|
+
relay proxy with [testcontainers](https://testcontainers.com) and evaluates
|
|
150
|
+
through the public OpenFeature API in both evaluation modes, asserting the same
|
|
151
|
+
expectations as the mocked tests — a mock that drifts from what the relay proxy
|
|
152
|
+
serves fails there.
|
|
153
|
+
|
|
154
|
+
The `relay_proxy` fixture (`tests/conftest.py`) generates the proxy's
|
|
155
|
+
configuration from `tests/mock_responses/config/valid-all-types.json`, the same
|
|
156
|
+
fixture the mocked in-process tests replay, and mounts it at `/goff/` (one of the
|
|
157
|
+
relay proxy's default config locations). It waits on `GET /health` and binds a
|
|
158
|
+
random host port, so it never collides with a proxy already running on 1031.
|
|
159
|
+
|
|
160
|
+
The module **skips** when no Docker daemon is reachable. CI runs it by default.
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.0](https://github.com/thomaspoignant/go-feature-flag/compare/openfeature/providers/python-provider/v1.2.0...openfeature/providers/python-provider/v1.3.0) (2026-09-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🚀 New Features
|
|
7
|
+
|
|
8
|
+
* **python-provider:** survive WASM traps and harden the wasm evaluation module ([bc37081](https://github.com/thomaspoignant/go-feature-flag/commit/bc37081f0314b18ffb41ebd74aea1d3b9872dd67))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### 🐛 Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **sonar:** resolve SonarCloud issues reported on main ([#5797](https://github.com/thomaspoignant/go-feature-flag/issues/5797)) ([7314d1b](https://github.com/thomaspoignant/go-feature-flag/commit/7314d1b1934b25b169405dfc3a2ea0294b19924f))
|
|
14
|
+
* **sonar:** resolve SonarCloud issues reported on main ([#5799](https://github.com/thomaspoignant/go-feature-flag/issues/5799)) ([e2da54a](https://github.com/thomaspoignant/go-feature-flag/commit/e2da54a0a7ebe44ade2d772d83c994d05af0228b))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### 🔧 Chores
|
|
18
|
+
|
|
19
|
+
* Bump github.com/aws/aws-sdk-go-v2 from 1.43.2 to 1.43.3 ([#5804](https://github.com/thomaspoignant/go-feature-flag/issues/5804)) ([5cd3520](https://github.com/thomaspoignant/go-feature-flag/commit/5cd352036f7fcc22edbe34d47af85f1c675eec9f))
|
|
20
|
+
* Bump github.com/aws/aws-sdk-go-v2 from 1.43.5 to 1.43.7 ([#5923](https://github.com/thomaspoignant/go-feature-flag/issues/5923)) ([6e64093](https://github.com/thomaspoignant/go-feature-flag/commit/6e640937a0d8d2f107f9fc9af57386cf40672853))
|
|
21
|
+
* Bump github.com/aws/aws-sdk-go-v2/service/s3 ([#5577](https://github.com/thomaspoignant/go-feature-flag/issues/5577)) ([02d42d7](https://github.com/thomaspoignant/go-feature-flag/commit/02d42d7ca089071c263fe5283ad3d01ce6365025))
|
|
22
|
+
* Bump github.com/aws/aws-sdk-go-v2/service/s3 ([#5594](https://github.com/thomaspoignant/go-feature-flag/issues/5594)) ([4784dba](https://github.com/thomaspoignant/go-feature-flag/commit/4784dba83fd00f8472cd4bf87ec94264df7c0bfa))
|
|
23
|
+
* Bump github.com/aws/aws-sdk-go-v2/service/s3 ([#5771](https://github.com/thomaspoignant/go-feature-flag/issues/5771)) ([e46119d](https://github.com/thomaspoignant/go-feature-flag/commit/e46119d953efe049174b5d87518824d1330fef8b))
|
|
24
|
+
* Bump github.com/aws/aws-sdk-go-v2/service/s3 ([#5840](https://github.com/thomaspoignant/go-feature-flag/issues/5840)) ([b11fb03](https://github.com/thomaspoignant/go-feature-flag/commit/b11fb03abcba68ee2fc82ed98f838747fe406dca))
|
|
25
|
+
* Bump github.com/aws/smithy-go from 1.27.5 to 1.27.6 ([#5807](https://github.com/thomaspoignant/go-feature-flag/issues/5807)) ([627e361](https://github.com/thomaspoignant/go-feature-flag/commit/627e3610e09f3bb48ed968d6d3d99a228a434ad8))
|
|
26
|
+
* Bump github.com/aws/smithy-go from 1.27.7 to 1.27.8 ([#5895](https://github.com/thomaspoignant/go-feature-flag/issues/5895)) ([c7f9192](https://github.com/thomaspoignant/go-feature-flag/commit/c7f9192f1a3e69d6b837b8750917275d2078444e))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### ⚙️ CI/CD
|
|
30
|
+
|
|
31
|
+
* install Python provider deps from the committed uv.lock ([#5708](https://github.com/thomaspoignant/go-feature-flag/issues/5708)) ([c88351c](https://github.com/thomaspoignant/go-feature-flag/commit/c88351c885d45eb96d0e202586b2dae9ca726e34))
|
|
32
|
+
|
|
3
33
|
## [1.2.0](https://github.com/thomaspoignant/go-feature-flag/compare/openfeature/providers/python-provider/v1.1.0...openfeature/providers/python-provider/v1.2.0) (2026-07-02)
|
|
4
34
|
|
|
5
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: gofeatureflag-python-provider
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: GO Feature Flag provider for OpenFeature
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/thomaspoignant/go-feature-flag/issues
|
|
6
6
|
Project-URL: Source Code, https://github.com/thomaspoignant/go-feature-flag/tree/main/openfeature/providers/python-provider
|
|
@@ -11,11 +11,11 @@ Keywords: feature flags,feature toggles,go-feature-flag,gofeatureflag,open-featu
|
|
|
11
11
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
|
-
Requires-Dist: openfeature-provider-ofrep>=0.
|
|
14
|
+
Requires-Dist: openfeature-provider-ofrep>=0.3.0
|
|
15
15
|
Requires-Dist: openfeature-sdk<0.11.0,>=0.8.4
|
|
16
|
-
Requires-Dist: pydantic>=2.
|
|
17
|
-
Requires-Dist: urllib3>=2.
|
|
18
|
-
Requires-Dist: wasmtime>=
|
|
16
|
+
Requires-Dist: pydantic>=2.13.4
|
|
17
|
+
Requires-Dist: urllib3>=2.7.0
|
|
18
|
+
Requires-Dist: wasmtime>=47.0.1
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
21
21
|
# GO Feature Flag Python Provider
|
|
@@ -133,16 +133,60 @@ else:
|
|
|
133
133
|
|--------|------|---------|-------------|
|
|
134
134
|
| `endpoint` | `str` | _(required)_ | URL of the GO Feature Flag relay proxy |
|
|
135
135
|
| `evaluation_type` | `EvaluationType` | `INPROCESS` | Evaluation mode: `INPROCESS` or `REMOTE` |
|
|
136
|
-
| `
|
|
136
|
+
| `data_collector_base_url` | `str` | `endpoint` | Base URL of the data collector only. Replaces the whole base — scheme, host, port and path prefix. Flag configuration and evaluation keep using `endpoint` |
|
|
137
|
+
| `timeout` | `int` | `10000` | Timeout (ms) for flag configuration, remote evaluation and data collection requests. Carried by the HTTP client the provider builds, so a custom `urllib3_pool_manager` replaces it with its own |
|
|
137
138
|
| `data_flush_interval` | `int` | `60000` | Interval (ms) to flush usage data to the relay proxy |
|
|
138
139
|
| `disable_data_collection` | `bool` | `False` | Set to `True` to disable usage analytics |
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `exporter_metadata` | `dict` | `{}` | Static metadata attached to evaluation events |
|
|
144
|
-
| `max_pending_events` | `int` | `10000` | Maximum buffered events before a forced flush |
|
|
140
|
+
| `flag_config_poll_interval_seconds` | `int` | `120` | Polling interval (seconds) for flag configuration _(in-process mode)_ |
|
|
141
|
+
| `api_key` | `str` | `None` | API key for authenticated relay proxy requests, sent as `Authorization: Bearer` |
|
|
142
|
+
| `exporter_metadata` | `dict` | `{}` | Static metadata attached to evaluation events. Values must be `str`, `bool`, `int` or `float`. The reserved keys `provider` and `openfeature` are always added and win over your values |
|
|
143
|
+
| `max_pending_events` | `int` | `10000` | Buffered events that trigger an immediate flush. The buffer holds up to twice this many, above which the oldest are dropped |
|
|
145
144
|
| `wasm_file_path` | `str` | `None` | Path to a custom WASM/WASI evaluation binary _(in-process mode, uses bundled binary by default)_ |
|
|
146
|
-
| `wasm_pool_size` | `int` |
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
145
|
+
| `wasm_pool_size` | `int` | CPU core count | Pool size for concurrent WASM evaluation instances _(in-process mode)_ |
|
|
146
|
+
| `evaluation_flag_list` | `list[str]` | `None` | Restrict the fetched flag configuration to these keys. Unset or empty means all flags _(in-process mode)_ |
|
|
147
|
+
| `custom_headers` | `dict[str, str]` | `None` | Extra headers on every relay proxy request, for deployments behind a gateway with its own authentication. Applied before the provider's own headers, so a configured `api_key` wins over a custom `Authorization` |
|
|
148
|
+
| `log_level` | `str\|int` | `"WARNING"` | Logging level (`"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"`). Applied only if your application has not configured the `gofeatureflag_python_provider` logger itself — see [Logging](#logging) |
|
|
149
|
+
| `urllib3_pool_manager` | `urllib3.PoolManager` | `None` | Custom HTTP client for flag configuration and data collection. Remote evaluation uses the OFREP client, which builds its own |
|
|
150
|
+
|
|
151
|
+
Usage analytics are collected only while the provider is running — between `initialize()`
|
|
152
|
+
(called for you by `set_provider`) and `shutdown()`. Evaluations outside that window are not
|
|
153
|
+
recorded: no flush runs to deliver them, and a buffer left to fill would post to the collector
|
|
154
|
+
after `shutdown()` reported it was done. Each such period logs one warning, and the number of
|
|
155
|
+
events dropped is reported if the provider is started again.
|
|
156
|
+
|
|
157
|
+
## Logging
|
|
158
|
+
|
|
159
|
+
Every module logs through the standard library under the **`gofeatureflag_python_provider`**
|
|
160
|
+
logger, with one child logger per module (`gofeatureflag_python_provider.wasm.evaluate_wasm`,
|
|
161
|
+
`gofeatureflag_python_provider.services.event_publisher`, and so on). Attach your handler to the
|
|
162
|
+
package logger to capture all of it:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
import logging
|
|
166
|
+
|
|
167
|
+
logging.getLogger("gofeatureflag_python_provider").setLevel(logging.INFO)
|
|
168
|
+
logging.getLogger("gofeatureflag_python_provider").addHandler(logging.StreamHandler())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The provider ships a `NullHandler` on that logger, so it stays silent until your application
|
|
172
|
+
configures logging.
|
|
173
|
+
|
|
174
|
+
Two things worth knowing:
|
|
175
|
+
|
|
176
|
+
- **`log_level` yields to your configuration.** The provider applies it to the package logger only
|
|
177
|
+
when nothing has set a level there yet, because that level is process-wide state. If you
|
|
178
|
+
configure the logger yourself (directly or through `logging.config.dictConfig`), your setting
|
|
179
|
+
wins. If several providers are created with different `log_level` values, the first one applies.
|
|
180
|
+
- **Exceptions raised inside hooks are logged by the OpenFeature SDK**, under its own `openfeature`
|
|
181
|
+
logger, not this one. `log_level` does not affect them — configure `logging.getLogger("openfeature")`
|
|
182
|
+
if you need to see them.
|
|
183
|
+
|
|
184
|
+
## Conformance
|
|
185
|
+
|
|
186
|
+
This provider targets version **1.0** of the
|
|
187
|
+
[GO Feature Flag Provider Specification](https://gofeatureflag.org/specification/openfeature-provider),
|
|
188
|
+
exposed as `gofeatureflag_python_provider.__specification_version__`.
|
|
189
|
+
|
|
190
|
+
The evaluation engine it is pinned to is recorded in
|
|
191
|
+
`gofeatureflag_python_provider/wasm/_wasi_version.txt`, which is the single source of truth
|
|
192
|
+
for the bundled WASI binary version.
|
|
@@ -113,16 +113,60 @@ else:
|
|
|
113
113
|
|--------|------|---------|-------------|
|
|
114
114
|
| `endpoint` | `str` | _(required)_ | URL of the GO Feature Flag relay proxy |
|
|
115
115
|
| `evaluation_type` | `EvaluationType` | `INPROCESS` | Evaluation mode: `INPROCESS` or `REMOTE` |
|
|
116
|
-
| `
|
|
116
|
+
| `data_collector_base_url` | `str` | `endpoint` | Base URL of the data collector only. Replaces the whole base — scheme, host, port and path prefix. Flag configuration and evaluation keep using `endpoint` |
|
|
117
|
+
| `timeout` | `int` | `10000` | Timeout (ms) for flag configuration, remote evaluation and data collection requests. Carried by the HTTP client the provider builds, so a custom `urllib3_pool_manager` replaces it with its own |
|
|
117
118
|
| `data_flush_interval` | `int` | `60000` | Interval (ms) to flush usage data to the relay proxy |
|
|
118
119
|
| `disable_data_collection` | `bool` | `False` | Set to `True` to disable usage analytics |
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `exporter_metadata` | `dict` | `{}` | Static metadata attached to evaluation events |
|
|
124
|
-
| `max_pending_events` | `int` | `10000` | Maximum buffered events before a forced flush |
|
|
120
|
+
| `flag_config_poll_interval_seconds` | `int` | `120` | Polling interval (seconds) for flag configuration _(in-process mode)_ |
|
|
121
|
+
| `api_key` | `str` | `None` | API key for authenticated relay proxy requests, sent as `Authorization: Bearer` |
|
|
122
|
+
| `exporter_metadata` | `dict` | `{}` | Static metadata attached to evaluation events. Values must be `str`, `bool`, `int` or `float`. The reserved keys `provider` and `openfeature` are always added and win over your values |
|
|
123
|
+
| `max_pending_events` | `int` | `10000` | Buffered events that trigger an immediate flush. The buffer holds up to twice this many, above which the oldest are dropped |
|
|
125
124
|
| `wasm_file_path` | `str` | `None` | Path to a custom WASM/WASI evaluation binary _(in-process mode, uses bundled binary by default)_ |
|
|
126
|
-
| `wasm_pool_size` | `int` |
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
125
|
+
| `wasm_pool_size` | `int` | CPU core count | Pool size for concurrent WASM evaluation instances _(in-process mode)_ |
|
|
126
|
+
| `evaluation_flag_list` | `list[str]` | `None` | Restrict the fetched flag configuration to these keys. Unset or empty means all flags _(in-process mode)_ |
|
|
127
|
+
| `custom_headers` | `dict[str, str]` | `None` | Extra headers on every relay proxy request, for deployments behind a gateway with its own authentication. Applied before the provider's own headers, so a configured `api_key` wins over a custom `Authorization` |
|
|
128
|
+
| `log_level` | `str\|int` | `"WARNING"` | Logging level (`"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"`). Applied only if your application has not configured the `gofeatureflag_python_provider` logger itself — see [Logging](#logging) |
|
|
129
|
+
| `urllib3_pool_manager` | `urllib3.PoolManager` | `None` | Custom HTTP client for flag configuration and data collection. Remote evaluation uses the OFREP client, which builds its own |
|
|
130
|
+
|
|
131
|
+
Usage analytics are collected only while the provider is running — between `initialize()`
|
|
132
|
+
(called for you by `set_provider`) and `shutdown()`. Evaluations outside that window are not
|
|
133
|
+
recorded: no flush runs to deliver them, and a buffer left to fill would post to the collector
|
|
134
|
+
after `shutdown()` reported it was done. Each such period logs one warning, and the number of
|
|
135
|
+
events dropped is reported if the provider is started again.
|
|
136
|
+
|
|
137
|
+
## Logging
|
|
138
|
+
|
|
139
|
+
Every module logs through the standard library under the **`gofeatureflag_python_provider`**
|
|
140
|
+
logger, with one child logger per module (`gofeatureflag_python_provider.wasm.evaluate_wasm`,
|
|
141
|
+
`gofeatureflag_python_provider.services.event_publisher`, and so on). Attach your handler to the
|
|
142
|
+
package logger to capture all of it:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import logging
|
|
146
|
+
|
|
147
|
+
logging.getLogger("gofeatureflag_python_provider").setLevel(logging.INFO)
|
|
148
|
+
logging.getLogger("gofeatureflag_python_provider").addHandler(logging.StreamHandler())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The provider ships a `NullHandler` on that logger, so it stays silent until your application
|
|
152
|
+
configures logging.
|
|
153
|
+
|
|
154
|
+
Two things worth knowing:
|
|
155
|
+
|
|
156
|
+
- **`log_level` yields to your configuration.** The provider applies it to the package logger only
|
|
157
|
+
when nothing has set a level there yet, because that level is process-wide state. If you
|
|
158
|
+
configure the logger yourself (directly or through `logging.config.dictConfig`), your setting
|
|
159
|
+
wins. If several providers are created with different `log_level` values, the first one applies.
|
|
160
|
+
- **Exceptions raised inside hooks are logged by the OpenFeature SDK**, under its own `openfeature`
|
|
161
|
+
logger, not this one. `log_level` does not affect them — configure `logging.getLogger("openfeature")`
|
|
162
|
+
if you need to see them.
|
|
163
|
+
|
|
164
|
+
## Conformance
|
|
165
|
+
|
|
166
|
+
This provider targets version **1.0** of the
|
|
167
|
+
[GO Feature Flag Provider Specification](https://gofeatureflag.org/specification/openfeature-provider),
|
|
168
|
+
exposed as `gofeatureflag_python_provider.__specification_version__`.
|
|
169
|
+
|
|
170
|
+
The evaluation engine it is pinned to is recorded in
|
|
171
|
+
`gofeatureflag_python_provider/wasm/_wasi_version.txt`, which is the single source of truth
|
|
172
|
+
for the bundled WASI binary version.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
# Every module in this package logs through `logging.getLogger(__name__)`, so all
|
|
4
|
+
# records land under this logger. A library must not impose output on the
|
|
5
|
+
# application embedding it: the NullHandler keeps Python from falling back to
|
|
6
|
+
# "no handlers could be found" when the application configured no logging at all,
|
|
7
|
+
# while leaving propagation intact so an application that did configure it still
|
|
8
|
+
# receives everything.
|
|
9
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
10
|
+
|
|
11
|
+
__version__ = "1.3.0"
|
|
12
|
+
|
|
13
|
+
# Version of the GO Feature Flag Provider Specification this provider targets.
|
|
14
|
+
# https://gofeatureflag.org/specification/openfeature-provider
|
|
15
|
+
#
|
|
16
|
+
# The evaluation engine it is pinned to is recorded separately, in
|
|
17
|
+
# gofeatureflag_python_provider/wasm/_wasi_version.txt.
|
|
18
|
+
__specification_version__ = "1.0"
|
|
@@ -18,7 +18,7 @@ class AbstractEvaluator(ABC):
|
|
|
18
18
|
def initialize(
|
|
19
19
|
self, evaluation_context: Optional[EvaluationContext] = None
|
|
20
20
|
) -> None:
|
|
21
|
-
"""Initialize the evaluator (e.g.
|
|
21
|
+
"""Initialize the evaluator (e.g. flag configuration polling, WASM runtime)."""
|
|
22
22
|
...
|
|
23
23
|
|
|
24
24
|
@abstractmethod
|