Skip to content

Commit 42d56c4

Browse files
committed
fix: Exclude identities when PERCENTAGE_SPLIT trait is undefined
1 parent 8e07687 commit 42d56c4

File tree

1 file changed

+53
-13
lines changed

1 file changed

+53
-13
lines changed

flag_engine/segments/evaluator.py

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,25 +228,57 @@ def context_matches_condition(
228228
condition: SegmentCondition,
229229
segment_key: SupportsStr,
230230
) -> bool:
231-
context_value = (
232-
get_context_value(context, condition_property)
233-
if (condition_property := condition.get("property"))
234-
else None
235-
)
236-
237-
if condition["operator"] == constants.PERCENTAGE_SPLIT:
238-
if context_value is not None:
239-
object_ids = [segment_key, context_value]
231+
context_value: ContextValue
232+
condition_property = condition["property"]
233+
condition_operator = condition["operator"]
234+
235+
if condition_operator == constants.PERCENTAGE_SPLIT and (not condition_property):
236+
# Currently, the only supported condition with a blank property
237+
# is percentage split.
238+
# In this case, we use the identity key as context value.
239+
# This is mainly to support legacy segments created before
240+
# we introduced JSONPath support.
241+
context_value = _get_identity_key(context)
242+
else:
243+
context_value = get_context_value(context, condition_property)
244+
245+
if condition_operator == constants.IN:
246+
if isinstance(segment_value := condition["value"], list):
247+
in_values = segment_value
240248
else:
241-
object_ids = [segment_key, get_context_value(context, "$.identity.key")]
249+
try:
250+
in_values = json.loads(segment_value)
251+
# Only accept JSON lists.
252+
# Ideally, we should use something like pydantic.TypeAdapter[list[str]],
253+
# but we aim to ditch the pydantic dependency in the future.
254+
if not isinstance(in_values, list):
255+
raise ValueError
256+
except ValueError:
257+
in_values = segment_value.split(",")
258+
in_values = [str(value) for value in in_values]
259+
# Guard against comparing boolean values to numeric strings.
260+
if isinstance(context_value, int) and not (
261+
context_value is True or context_value is False
262+
):
263+
context_value = str(context_value)
264+
return context_value in in_values
242265

243-
float_value = float(condition["value"])
266+
if condition_operator == constants.PERCENTAGE_SPLIT:
267+
if context_value is None:
268+
return False
269+
270+
object_ids = [segment_key, context_value]
271+
272+
try:
273+
float_value = float(condition["value"])
274+
except ValueError:
275+
return False
244276
return get_hashed_percentage_for_object_ids(object_ids) <= float_value
245277

246-
if condition["operator"] == constants.IS_NOT_SET:
278+
if condition_operator == constants.IS_NOT_SET:
247279
return context_value is None
248280

249-
if condition["operator"] == constants.IS_SET:
281+
if condition_operator == constants.IS_SET:
250282
return context_value is not None
251283

252284
return (
@@ -368,6 +400,14 @@ def inner(
368400
return inner
369401

370402

403+
def _get_identity_key(
404+
context: EvaluationContext,
405+
) -> typing.Optional[str]:
406+
if identity_context := context.get("identity"):
407+
return identity_context.get("key")
408+
return None
409+
410+
371411
MATCHERS_BY_OPERATOR: typing.Dict[
372412
ConditionOperator, typing.Callable[[typing.Optional[str], ContextValue], bool]
373413
] = {

0 commit comments

Comments
 (0)