|
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_boolean_value / get_string_value / ... below. |
| 10 | + These evaluate anonymously and are for background jobs and other code with no CouchersContext. |
| 11 | +
|
| 12 | +Both paths share the enabled / pass-all-gates gating helpers here. setup_experimentation() is called |
| 13 | +once at process startup. |
10 | 14 | """ |
11 | 15 |
|
12 | 16 | import json |
13 | 17 | import logging |
14 | 18 | import threading |
| 19 | +from collections.abc import Callable |
15 | 20 | from typing import Any |
16 | 21 |
|
17 | 22 | import urllib3 |
@@ -170,3 +175,46 @@ def on_experiment_viewed(experiment: Experiment, result: Result, **kwargs: Any) |
170 | 175 | savedGroups=saved_groups, |
171 | 176 | on_experiment_viewed=on_experiment_viewed, |
172 | 177 | ) |
| 178 | + |
| 179 | + |
| 180 | +def _global_evaluator() -> GrowthBook: |
| 181 | + """Build an anonymous evaluator for flag evaluation with no user/request context.""" |
| 182 | + return _create_evaluator(None) |
| 183 | + |
| 184 | + |
| 185 | +# These two helpers are the single home of the gating logic, shared by the global functions below |
| 186 | +# and by CouchersContext (which passes its own cached per-request evaluator). get_evaluator is only |
| 187 | +# invoked once gating passes, so it stays lazy. |
| 188 | +def _feature_value[T](flag_key: str, default: T, get_evaluator: Callable[[], GrowthBook]) -> T: |
| 189 | + if not config["EXPERIMENTATION_ENABLED"]: |
| 190 | + return default |
| 191 | + return get_evaluator().get_feature_value(flag_key, default) # type: ignore[no-any-return] |
| 192 | + |
| 193 | + |
| 194 | +def _boolean_value(flag_key: str, default: bool, get_evaluator: Callable[[], GrowthBook]) -> bool: |
| 195 | + if config["EXPERIMENTATION_PASS_ALL_GATES"]: |
| 196 | + return True |
| 197 | + return _feature_value(flag_key, default, get_evaluator) |
| 198 | + |
| 199 | + |
| 200 | +# Global (no user/request) flag evaluation. Mirrors the CouchersContext.get_*_value API but |
| 201 | +# evaluates anonymously: experiments and percentage rollouts are skipped (no user to bucket), so |
| 202 | +# flags fall through to their in-code defaults unless a rule forces a value globally. |
| 203 | +def get_boolean_value(flag_key: str, default: bool) -> bool: |
| 204 | + return _boolean_value(flag_key, default, _global_evaluator) |
| 205 | + |
| 206 | + |
| 207 | +def get_string_value(flag_key: str, default: str) -> str: |
| 208 | + return _feature_value(flag_key, default, _global_evaluator) |
| 209 | + |
| 210 | + |
| 211 | +def get_integer_value(flag_key: str, default: int) -> int: |
| 212 | + return _feature_value(flag_key, default, _global_evaluator) |
| 213 | + |
| 214 | + |
| 215 | +def get_float_value(flag_key: str, default: float) -> float: |
| 216 | + return _feature_value(flag_key, default, _global_evaluator) |
| 217 | + |
| 218 | + |
| 219 | +def get_object_value[T](flag_key: str, default: T) -> T: |
| 220 | + return _feature_value(flag_key, default, _global_evaluator) |
0 commit comments