Skip to content

Commit

Permalink
Add prerequisite flag support (still some failing tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdorn committed Apr 4, 2024
1 parent a838206 commit 9a78d83
Showing 1 changed file with 69 additions and 1 deletion.
70 changes: 69 additions & 1 deletion growthbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,18 @@ def __init__(
groups: list = None,
force: int = None,
hashAttribute: str = "id",
fallbackAttribute: str = None,
hashVersion: int = None,
ranges: List[Tuple[float, float]] = None,
meta: List[VariationMeta] = None,
filters: List[Filter] = None,
seed: str = None,
name: str = None,
phase: str = None,
disableStickyBucketing: bool = False,
bucketVersion: int = None,
minBucketVersion: int = None,
parentConditions: List[dict] = None,
) -> None:
self.key = key
self.variations = variations
Expand All @@ -400,13 +405,18 @@ def __init__(
self.namespace = namespace
self.force = force
self.hashAttribute = hashAttribute
self.fallbackAttribute = fallbackAttribute
self.hashVersion = hashVersion or 1
self.ranges = ranges
self.meta = meta
self.filters = filters
self.seed = seed
self.name = name
self.phase = phase
self.disableStickyBucketing = disableStickyBucketing
self.bucketVersion = bucketVersion
self.minBucketVersion = minBucketVersion
self.parentConditions = parentConditions

# Deprecated properties
self.status = status
Expand All @@ -433,6 +443,18 @@ def to_dict(self):
"name": self.name,
"phase": self.phase,
}

if self.fallbackAttribute:
obj["fallbackAttribute"] = self.fallbackAttribute
if self.disableStickyBucketing:
obj["disableStickyBucketing"] = True
if self.bucketVersion:
obj["bucketVersion"] = self.bucketVersion
if self.minBucketVersion:
obj["minBucketVersion"] = self.minBucketVersion
if self.parentConditions:
obj["parentConditions"] = self.parentConditions

return obj

def update(self, data: dict) -> None:
Expand Down Expand Up @@ -469,6 +491,7 @@ def __init__(
featureId: Optional[str],
meta: VariationMeta = None,
bucket: float = None,
stickyBucketUsed: bool = False,
) -> None:
self.variationId = variationId
self.inExperiment = inExperiment
Expand All @@ -478,6 +501,7 @@ def __init__(
self.hashValue = hashValue
self.featureId = featureId or None
self.bucket = bucket
self.stickyBucketUsed = stickyBucketUsed

self.key = str(variationId)
self.name = ""
Expand All @@ -501,6 +525,7 @@ def to_dict(self) -> dict:
"hashAttribute": self.hashAttribute,
"hashValue": self.hashValue,
"key": self.key,
"stickyBucketUsed": self.stickyBucketUsed,
}

if self.bucket is not None:
Expand Down Expand Up @@ -560,6 +585,7 @@ def __init__(
namespace: Tuple[str, float, float] = None,
force=None,
hashAttribute: str = "id",
fallbackAttribute: str = None,
hashVersion: int = None,
range: Tuple[float, float] = None,
ranges: List[Tuple[float, float]] = None,
Expand All @@ -568,6 +594,10 @@ def __init__(
seed: str = None,
name: str = None,
phase: str = None,
disableStickyBucketing: bool = False,
bucketVersion: int = None,
minBucketVersion: int = None,
parentConditions: List[dict] = None,
) -> None:
self.id = id
self.key = key
Expand All @@ -578,6 +608,7 @@ def __init__(
self.namespace = namespace
self.force = force
self.hashAttribute = hashAttribute
self.fallbackAttribute = fallbackAttribute
self.hashVersion = hashVersion or 1
self.range = range
self.ranges = ranges
Expand All @@ -586,6 +617,10 @@ def __init__(
self.seed = seed
self.name = name
self.phase = phase
self.disableStickyBucketing = disableStickyBucketing
self.bucketVersion = bucketVersion
self.minBucketVersion = minBucketVersion
self.parentConditions = parentConditions

def to_dict(self) -> dict:
data: Dict[str, Any] = {}
Expand Down Expand Up @@ -623,6 +658,16 @@ def to_dict(self) -> dict:
data["name"] = self.name
if self.phase is not None:
data["phase"] = self.phase
if self.fallbackAttribute:
obj["fallbackAttribute"] = self.fallbackAttribute
if self.disableStickyBucketing:
obj["disableStickyBucketing"] = True
if self.bucketVersion:
obj["bucketVersion"] = self.bucketVersion
if self.minBucketVersion:
obj["minBucketVersion"] = self.minBucketVersion
if self.parentConditions:
obj["parentConditions"] = self.parentConditions

return data

Expand Down Expand Up @@ -784,7 +829,6 @@ def _get_features_url(api_host: str, client_key: str) -> str:
# Singleton instance
feature_repo = FeatureRepository()


class GrowthBook(object):
def __init__(
self,
Expand Down Expand Up @@ -915,14 +959,37 @@ def get_feature_value(self, key: str, fallback):
def evalFeature(self, key: str) -> FeatureResult:
return self.eval_feature(key)

def eval_prereqs(self, parentConditions: List[dict], stack: Set[str]) -> str:
for parentCondition in parentConditions:
parentValue = self._eval_feature(parentCondition["feature"], stack).value
if not evalConditionValue(parentCondition["condition"], {'value': parentValue}):
if parentCondition.get("gate", False):
return "gate"
return "fail"
return "pass"

def eval_feature(self, key: str) -> FeatureResult:
return self._eval_feature(key, set())

def _eval_feature(self, key: str, stack: Set[str]) -> FeatureResult:
logger.debug("Evaluating feature %s", key)
if key not in self._features:
logger.warning("Unknown feature %s", key)
return FeatureResult(None, "unknownFeature")

if key in stack:
return FeatureResult(None, "cyclicPrerequisite")
stack.add(key)

feature = self._features[key]
for rule in feature.rules:
if (rule.parentConditions):
prereq_res = self.eval_prereqs(rule.parentConditions, stack)
if prereq_res == "gate":
return FeatureResult(None, "prerequisite")
if prereq_res == "fail":
continue

if rule.condition:
if not evalCondition(self._attributes, rule.condition):
logger.debug(
Expand Down Expand Up @@ -970,6 +1037,7 @@ def eval_feature(self, key: str) -> FeatureResult:
phase=rule.phase,
seed=rule.seed,
filters=rule.filters,
condition=rule.condition,
)

result = self._run(exp, key)
Expand Down

0 comments on commit 9a78d83

Please sign in to comment.