|
3 | 3 |
|
4 | 4 | Uses GrowthBook under the hood, but abstracts the implementation details. |
5 | 5 |
|
6 | | -Don't evaluate flags by calling into this module directly - go through the CouchersContext methods |
7 | | -(context.get_boolean_value, get_string_value, etc.), which own the per-request evaluator cache and |
8 | | -the enabled / pass-all-gates gating. The underscore-prefixed helpers here are internal to that |
9 | | -wiring; setup_experimentation() is the only public entry point, called once at process startup. |
| 6 | +Two ways to evaluate a flag: |
| 7 | + - Per-user/request: use the CouchersContext methods (context.get_boolean_value, get_string_value, |
| 8 | + etc.), which evaluate for the context's user and own the per-request evaluator cache. |
| 9 | + - Global (no user/request): use the module-level get_global_boolean_value / get_global_string_value |
| 10 | + / ... below. Use these ONLY when there is genuinely no user to evaluate for and no way to thread |
| 11 | + one through - per-user evaluation is impossible here, not merely that you don't expect the value |
| 12 | + to vary per user. Whenever a user is (or could reasonably be) available, use the context: only the |
| 13 | + per-user path can do percentage rollouts, experiments, and feature-usage tracking. |
| 14 | +
|
| 15 | +Both paths share the enabled / pass-all-gates gating helpers here. setup_experimentation() is called |
| 16 | +once at process startup. |
10 | 17 | """ |
11 | 18 |
|
12 | 19 | import json |
13 | 20 | import logging |
14 | 21 | import threading |
| 22 | +from collections.abc import Callable |
15 | 23 | from typing import Any |
16 | 24 |
|
17 | 25 | import urllib3 |
@@ -180,3 +188,49 @@ def on_feature_usage(key: str, result: FeatureResult, *args: Any, **kwargs: Any) |
180 | 188 | on_experiment_viewed=on_experiment_viewed, |
181 | 189 | on_feature_usage=on_feature_usage, |
182 | 190 | ) |
| 191 | + |
| 192 | + |
| 193 | +def _global_evaluator() -> GrowthBook: |
| 194 | + """Build an anonymous evaluator for flag evaluation with no user/request context.""" |
| 195 | + return _create_evaluator(None) |
| 196 | + |
| 197 | + |
| 198 | +# These two helpers are the single home of the gating logic, shared by the global functions below |
| 199 | +# and by CouchersContext (which passes its own cached per-request evaluator). get_evaluator is only |
| 200 | +# invoked once gating passes, so it stays lazy. |
| 201 | +def _feature_value[T](flag_key: str, default: T, get_evaluator: Callable[[], GrowthBook]) -> T: |
| 202 | + if not config["EXPERIMENTATION_ENABLED"]: |
| 203 | + return default |
| 204 | + return get_evaluator().get_feature_value(flag_key, default) # type: ignore[no-any-return] |
| 205 | + |
| 206 | + |
| 207 | +def _boolean_value(flag_key: str, default: bool, get_evaluator: Callable[[], GrowthBook]) -> bool: |
| 208 | + if config["EXPERIMENTATION_PASS_ALL_GATES"]: |
| 209 | + return True |
| 210 | + return _feature_value(flag_key, default, get_evaluator) |
| 211 | + |
| 212 | + |
| 213 | +# Global (no-user) flag evaluation. Use these ONLY when there is genuinely no user to evaluate for and |
| 214 | +# no way to thread one through - per-user evaluation is impossible here, not merely that you don't |
| 215 | +# expect the value to vary per user. If a user is (or could reasonably be) available, use the |
| 216 | +# CouchersContext methods instead: only the per-user path does percentage rollouts, experiments, and |
| 217 | +# feature-usage tracking. With no user to bucket, rollouts and experiments are skipped and flags fall |
| 218 | +# through to their in-code defaults unless a rule forces a value globally. |
| 219 | +def get_global_boolean_value(flag_key: str, default: bool) -> bool: |
| 220 | + return _boolean_value(flag_key, default, _global_evaluator) |
| 221 | + |
| 222 | + |
| 223 | +def get_global_string_value(flag_key: str, default: str) -> str: |
| 224 | + return _feature_value(flag_key, default, _global_evaluator) |
| 225 | + |
| 226 | + |
| 227 | +def get_global_integer_value(flag_key: str, default: int) -> int: |
| 228 | + return _feature_value(flag_key, default, _global_evaluator) |
| 229 | + |
| 230 | + |
| 231 | +def get_global_float_value(flag_key: str, default: float) -> float: |
| 232 | + return _feature_value(flag_key, default, _global_evaluator) |
| 233 | + |
| 234 | + |
| 235 | +def get_global_object_value[T](flag_key: str, default: T) -> T: |
| 236 | + return _feature_value(flag_key, default, _global_evaluator) |
0 commit comments