Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 53 additions & 13 deletions flag_engine/segments/evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,57 @@ def context_matches_condition(
condition: SegmentCondition,
segment_key: SupportsStr,
) -> bool:
context_value = (
get_context_value(context, condition_property)
if (condition_property := condition.get("property"))
else None
)

if condition["operator"] == constants.PERCENTAGE_SPLIT:
if context_value is not None:
object_ids = [segment_key, context_value]
context_value: ContextValue
condition_property = condition["property"]
condition_operator = condition["operator"]

if condition_operator == constants.PERCENTAGE_SPLIT and (not condition_property):
# Currently, the only supported condition with a blank property
# is percentage split.
# In this case, we use the identity key as context value.
# This is mainly to support legacy segments created before
# we introduced JSONPath support.
context_value = _get_identity_key(context)
else:
context_value = get_context_value(context, condition_property)

if condition_operator == constants.IN:
if isinstance(segment_value := condition["value"], list):
in_values = segment_value
else:
object_ids = [segment_key, get_context_value(context, "$.identity.key")]
try:
in_values = json.loads(segment_value)
# Only accept JSON lists.
# Ideally, we should use something like pydantic.TypeAdapter[list[str]],
# but we aim to ditch the pydantic dependency in the future.
if not isinstance(in_values, list):
raise ValueError
except ValueError:
in_values = segment_value.split(",")
in_values = [str(value) for value in in_values]
# Guard against comparing boolean values to numeric strings.
if isinstance(context_value, int) and not (
context_value is True or context_value is False
):
context_value = str(context_value)
return context_value in in_values

float_value = float(condition["value"])
if condition_operator == constants.PERCENTAGE_SPLIT:
if context_value is None:
return False

object_ids = [segment_key, context_value]

try:
float_value = float(condition["value"])
except ValueError:
return False
return get_hashed_percentage_for_object_ids(object_ids) <= float_value

if condition["operator"] == constants.IS_NOT_SET:
if condition_operator == constants.IS_NOT_SET:
return context_value is None

if condition["operator"] == constants.IS_SET:
if condition_operator == constants.IS_SET:
return context_value is not None

return (
Expand Down Expand Up @@ -368,6 +400,14 @@ def inner(
return inner


def _get_identity_key(
context: EvaluationContext,
) -> typing.Optional[str]:
if identity_context := context.get("identity"):
return identity_context.get("key")
return None


MATCHERS_BY_OPERATOR: typing.Dict[
ConditionOperator, typing.Callable[[typing.Optional[str], ContextValue], bool]
] = {
Expand Down
Loading