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.
Files changed (105) hide show
  1. gofeatureflag_python_provider-1.3.0/AGENTS.md +160 -0
  2. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/CHANGELOG.md +30 -0
  3. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/PKG-INFO +60 -16
  4. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/README.md +54 -10
  5. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/__init__.py +18 -0
  6. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/abstract_evaluator.py +1 -1
  7. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/evaluator/inprocess_evaluator.py +668 -0
  8. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/remote_evaluator.py +5 -27
  9. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/evaluator/util.py +27 -0
  10. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/hooks/data_collector.py +126 -0
  11. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/hooks/enrich_evaluation_context.py +11 -1
  12. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/metadata.py +1 -1
  13. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/options.py +137 -0
  14. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/provider.py +61 -17
  15. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/services/__init__.py +1 -1
  16. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/services/api.py +69 -33
  17. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/event_publisher.py +291 -0
  18. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model/__init__.py +27 -0
  19. {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
  20. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/model/request_flag_configuration.py +11 -0
  21. 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
  22. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/services/ofrep.py +54 -0
  23. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/utils.py +34 -0
  24. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/__init__.py +25 -0
  25. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/_wasi_version.txt +1 -0
  26. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/__init__.py +21 -0
  27. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/evaluation_trap.py +10 -0
  28. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/function_not_found.py +5 -0
  29. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/invalid_result.py +5 -0
  30. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/not_loaded.py +5 -0
  31. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/errors/pool_timeout.py +10 -0
  32. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/evaluate_wasm.py +263 -0
  33. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_0.2.4.wasi +0 -0
  34. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/__init__.py +13 -0
  35. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/evaluation_response.py +23 -0
  36. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/flag_context.py +12 -0
  37. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/model/input.py +19 -0
  38. gofeatureflag_python_provider-1.3.0/gofeatureflag_python_provider/wasm/wasi_runtime.py +69 -0
  39. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/pyproject.toml +21 -24
  40. gofeatureflag_python_provider-1.3.0/tests/conftest.py +146 -0
  41. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_data_collector_hook.py +185 -9
  42. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_enrich_evaluation_context_hook.py +37 -0
  43. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_evaluate_wasm.py +282 -7
  44. gofeatureflag_python_provider-1.3.0/tests/test_evaluator_util.py +107 -0
  45. gofeatureflag_python_provider-1.3.0/tests/test_event_publisher.py +500 -0
  46. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_gofeatureflag_provider.py +59 -9
  47. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_gofeatureflag_provider_inprocess.py +0 -3
  48. gofeatureflag_python_provider-1.3.0/tests/test_inprocess_evaluator.py +1377 -0
  49. gofeatureflag_python_provider-1.3.0/tests/test_integration_relayproxy.py +263 -0
  50. gofeatureflag_python_provider-1.3.0/tests/test_logging.py +110 -0
  51. gofeatureflag_python_provider-1.3.0/tests/test_options.py +122 -0
  52. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_remote_evaluator_ofrep.py +67 -7
  53. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/test_services_api.py +206 -17
  54. gofeatureflag_python_provider-1.3.0/tests/test_utils.py +61 -0
  55. gofeatureflag_python_provider-1.3.0/tests/test_wasm_trap_diagnosis.py +489 -0
  56. gofeatureflag_python_provider-1.3.0/tests/wasm_helpers.py +63 -0
  57. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/uv.lock +172 -44
  58. gofeatureflag_python_provider-1.2.0/AGENTS.md +0 -148
  59. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/__init__.py +0 -1
  60. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/evaluator/inprocess_evaluator.py +0 -327
  61. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/hooks/data_collector.py +0 -83
  62. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/options.py +0 -97
  63. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/response_flag_evaluation.py +0 -16
  64. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/services/event_publisher.py +0 -157
  65. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/__init__.py +0 -15
  66. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/_wasi_version.txt +0 -1
  67. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/evaluate_wasm.py +0 -211
  68. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/gofeatureflag-evaluation_0.2.3.wasi +0 -0
  69. gofeatureflag_python_provider-1.2.0/gofeatureflag_python_provider/wasm/models.py +0 -40
  70. gofeatureflag_python_provider-1.2.0/tests/config.goff.yaml +0 -176
  71. gofeatureflag_python_provider-1.2.0/tests/conftest.py +0 -0
  72. gofeatureflag_python_provider-1.2.0/tests/docker-compose.yml +0 -15
  73. gofeatureflag_python_provider-1.2.0/tests/test_event_publisher.py +0 -156
  74. gofeatureflag_python_provider-1.2.0/tests/test_inprocess_evaluator.py +0 -568
  75. gofeatureflag_python_provider-1.2.0/tests/test_websocket_cache_invalidation.py +0 -121
  76. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/.gitignore +0 -0
  77. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/CLAUDE.md +0 -0
  78. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/clean.py +0 -0
  79. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/evaluator/__init__.py +0 -0
  80. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/exceptions.py +0 -0
  81. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/gofeatureflag_python_provider/hooks/__init__.py +0 -0
  82. {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
  83. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/hatch_build.py +0 -0
  84. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/.gitignore +0 -0
  85. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/__init__.py +0 -0
  86. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-all-types-config-change.json +0 -0
  87. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-all-types.json +0 -0
  88. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-flag-config.json +0 -0
  89. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/config/valid-scheduled-rollout.json +0 -0
  90. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/bool_targeting_match.json +0 -0
  91. {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
  92. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_bool.json +0 -0
  93. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_float.json +0 -0
  94. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_int.json +0 -0
  95. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_object.json +0 -0
  96. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/disabled_string.json +0 -0
  97. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/double_key.json +0 -0
  98. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/flag_not_found.json +0 -0
  99. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/integer_key.json +0 -0
  100. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/invalid_json_body.json +0 -0
  101. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/list_key.json +0 -0
  102. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/object_key.json +0 -0
  103. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/string_key.json +0 -0
  104. {gofeatureflag_python_provider-1.2.0 → gofeatureflag_python_provider-1.3.0}/tests/mock_responses/ofrep/unknown_reason.json +0 -0
  105. {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.4
1
+ Metadata-Version: 2.5
2
2
  Name: gofeatureflag-python-provider
3
- Version: 1.2.0
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.2.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.5.2
17
- Requires-Dist: urllib3>=2.1.0
18
- Requires-Dist: wasmtime>=41.0.0
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
- | `cache_size` | `int` | `10000` | Maximum number of flag evaluations kept in the LRU cache _(remote mode)_ |
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
- | `reconnect_interval` | `int` | `60` | WebSocket reconnect interval (seconds) _(remote mode)_ |
140
- | `disable_cache_invalidation` | `bool` | `False` | Disable WebSocket-based cache invalidation _(remote mode)_ |
141
- | `flag_config_poll_interval_seconds` | `int` | `10` | Polling interval (seconds) for flag configuration _(in-process mode)_ |
142
- | `api_key` | `str` | `None` | API key for authenticated relay proxy requests |
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` | `10` | Pool size for concurrent WASM evaluation instances _(in-process mode)_ |
147
- | `log_level` | `str\|int` | `"WARNING"` | Logging level (`"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"`) |
148
- | `urllib3_pool_manager` | `urllib3.PoolManager` | `None` | Custom HTTP client |
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
- | `cache_size` | `int` | `10000` | Maximum number of flag evaluations kept in the LRU cache _(remote mode)_ |
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
- | `reconnect_interval` | `int` | `60` | WebSocket reconnect interval (seconds) _(remote mode)_ |
120
- | `disable_cache_invalidation` | `bool` | `False` | Disable WebSocket-based cache invalidation _(remote mode)_ |
121
- | `flag_config_poll_interval_seconds` | `int` | `10` | Polling interval (seconds) for flag configuration _(in-process mode)_ |
122
- | `api_key` | `str` | `None` | API key for authenticated relay proxy requests |
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` | `10` | Pool size for concurrent WASM evaluation instances _(in-process mode)_ |
127
- | `log_level` | `str\|int` | `"WARNING"` | Logging level (`"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"`) |
128
- | `urllib3_pool_manager` | `urllib3.PoolManager` | `None` | Custom HTTP client |
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. cache, WebSocket, WASM)."""
21
+ """Initialize the evaluator (e.g. flag configuration polling, WASM runtime)."""
22
22
  ...
23
23
 
24
24
  @abstractmethod