|
1 | 1 | """Client for interacting with Harness FF server""" |
2 | 2 |
|
3 | 3 | import threading |
4 | | -from typing import Any, Callable, Dict, Optional |
| 4 | +from enum import Enum |
| 5 | +from typing import Any, Callable, Dict, Optional, Union |
5 | 6 |
|
6 | 7 | from tenacity import RetryError |
7 | 8 | from jwt import decode |
@@ -33,6 +34,14 @@ def __str__(self): |
33 | 34 | return f"MissingOrEmptyAPIKeyException: {self.message}" |
34 | 35 |
|
35 | 36 |
|
| 37 | +class FeatureFlagType(str, Enum): |
| 38 | + BOOLEAN = "boolean" |
| 39 | + INT_OR_FLOAT = "int" |
| 40 | + STRING = "string" |
| 41 | + JSON = "json" |
| 42 | + FLAG_NOT_FOUND = "flag_not_found" |
| 43 | + |
| 44 | + |
36 | 45 | class CfClient(object): |
37 | 46 | def __init__( |
38 | 47 | self, sdk_key: str, |
@@ -197,6 +206,21 @@ def authenticate(self): |
197 | 206 | additional_headers["Harness-AccountID"] = decoded["accountID"] |
198 | 207 | self._client = self._client.with_headers(additional_headers) |
199 | 208 |
|
| 209 | + def get_flag_type(self, identifier) -> Optional[FeatureFlagType]: |
| 210 | + if self._initialised_failed_reason[True] is not None: |
| 211 | + log.warning( |
| 212 | + "Failed to check flag type for flag '%s', reason: Client is " |
| 213 | + "not initialized", |
| 214 | + identifier) |
| 215 | + return FeatureFlagType("flag_not_found") |
| 216 | + kind = self._evaluator.get_kind(identifier) |
| 217 | + if not kind: |
| 218 | + log.warning( |
| 219 | + "Failed to check flag kind for flag '%s', reason: flag not " |
| 220 | + "found", identifier) |
| 221 | + return FeatureFlagType("flag_not_found") |
| 222 | + return FeatureFlagType(kind) |
| 223 | + |
200 | 224 | def bool_variation(self, identifier: str, target: Target, |
201 | 225 | default: bool) -> bool: |
202 | 226 | # If initialization has failed, then return the default variation |
@@ -291,6 +315,40 @@ def number_variation(self, identifier: str, target: Target, |
291 | 315 | "Reason: '%s'", identifier, default, str(ex)) |
292 | 316 | return default |
293 | 317 |
|
| 318 | + def int_or_float_variation(self, identifier: str, target: Target, |
| 319 | + default: Union[float, int]) -> Union[ |
| 320 | + float, int]: |
| 321 | + |
| 322 | + # If initialization has failed, then return the default variation |
| 323 | + # immediately |
| 324 | + if self._initialised_failed_reason[True] is not None: |
| 325 | + log.error( |
| 326 | + "SDKCODE:6001: Failed to evaluate int_or_float variation for " |
| 327 | + "flag '%s' and the default variation '%s' is being returned. " |
| 328 | + "Reason: `Client is not initialized: %s'", |
| 329 | + identifier, default, self._initialised_failed_reason[True]) |
| 330 | + return default |
| 331 | + |
| 332 | + try: |
| 333 | + variation = self._evaluator.evaluate( |
| 334 | + identifier, target, "int") |
| 335 | + |
| 336 | + # Only register metrics if analytics is enabled, |
| 337 | + # and sometimes when the SDK starts up we can |
| 338 | + # evaluate before the flag is cached which results in |
| 339 | + # an empty identifier. |
| 340 | + if self._config.enable_analytics and variation.identifier != "": |
| 341 | + self._analytics.enqueue(target, identifier, variation) |
| 342 | + |
| 343 | + return variation.int_or_float(target, identifier, default) |
| 344 | + |
| 345 | + except FlagKindMismatchException as ex: |
| 346 | + log.error( |
| 347 | + "SDKCODE:6001: Failed to evaluate int_or_float variation for " |
| 348 | + "flag '%s' and the default variation '%s' is being returned. " |
| 349 | + "Reason: '%s'", identifier, default, str(ex)) |
| 350 | + return default |
| 351 | + |
294 | 352 | def string_variation(self, identifier: str, target: Target, |
295 | 353 | default: str) -> str: |
296 | 354 |
|
|
0 commit comments