Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 7300f72

Browse files
authored
[ufc] fix string conversion (#51)
1 parent c6ce44d commit 7300f72

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

eppo_client/rules.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import json
12
import numbers
23
import re
3-
import semver
44
from enum import Enum
55
from typing import Any, List
66

7+
import semver
8+
79
from eppo_client.models import SdkBaseModel
8-
from eppo_client.types import ConditionValueType, SubjectAttributes
10+
from eppo_client.types import AttributeType, ConditionValueType, SubjectAttributes
911

1012

1113
class OperatorType(Enum):
@@ -49,20 +51,20 @@ def evaluate_condition(
4951
if subject_value is not None:
5052
if condition.operator == OperatorType.MATCHES:
5153
return isinstance(condition.value, str) and bool(
52-
re.search(condition.value, str(subject_value))
54+
re.search(condition.value, to_string(subject_value))
5355
)
54-
if condition.operator == OperatorType.NOT_MATCHES:
56+
elif condition.operator == OperatorType.NOT_MATCHES:
5557
return isinstance(condition.value, str) and not bool(
56-
re.search(condition.value, str(subject_value))
58+
re.search(condition.value, to_string(subject_value))
5759
)
5860
elif condition.operator == OperatorType.ONE_OF:
59-
return isinstance(condition.value, list) and str(subject_value) in [
61+
return isinstance(condition.value, list) and to_string(subject_value) in [
6062
str(value) for value in condition.value
6163
]
6264
elif condition.operator == OperatorType.NOT_ONE_OF:
63-
return isinstance(condition.value, list) and str(subject_value) not in [
64-
str(value) for value in condition.value
65-
]
65+
return isinstance(condition.value, list) and to_string(
66+
subject_value
67+
) not in [str(value) for value in condition.value]
6668
else:
6769
# Numeric operator: value could be numeric or semver.
6870
if isinstance(subject_value, numbers.Number):
@@ -119,3 +121,13 @@ def compare_semver(
119121
return semver.compare(attribute_value, condition_value) <= 0
120122

121123
return False
124+
125+
126+
def to_string(value: AttributeType) -> str:
127+
if isinstance(value, str):
128+
return value
129+
elif isinstance(value, bool):
130+
return "true" if value else "false"
131+
elif isinstance(value, float):
132+
return f"{value:.0f}" if value.is_integer() else str(value)
133+
return json.dumps(value)

eppo_client/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Dict, List, Union
22

33
ValueType = Union[str, int, float, bool]
4-
AttributeType = Union[str, int, float, bool]
4+
AttributeType = Union[str, int, float, bool, None]
55
ConditionValueType = Union[AttributeType, List[AttributeType]]
66
SubjectAttributes = Dict[str, AttributeType]
77
Action = str

test/rules_test.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Condition,
55
evaluate_condition,
66
matches_rule,
7+
to_string,
78
)
89

910
greater_than_condition = Condition(operator=OperatorType.GT, value=10, attribute="age")
@@ -137,10 +138,22 @@ def test_evaluate_condition_matches():
137138
Condition(operator=OperatorType.MATCHES, value="^test.*", attribute="email"),
138139
{"email": "[email protected]"},
139140
)
141+
assert evaluate_condition(
142+
Condition(operator=OperatorType.MATCHES, value="true", attribute="flag"),
143+
{"flag": True},
144+
)
145+
assert evaluate_condition(
146+
Condition(operator=OperatorType.MATCHES, value="false", attribute="flag"),
147+
{"flag": False},
148+
)
140149
assert not evaluate_condition(
141150
Condition(operator=OperatorType.MATCHES, value="^test.*", attribute="email"),
142151
{"email": "[email protected]"},
143152
)
153+
assert not evaluate_condition(
154+
Condition(operator=OperatorType.MATCHES, value="False", attribute="flag"),
155+
{"flag": False},
156+
)
144157

145158

146159
def test_evaluate_condition_matches_partial():
@@ -348,10 +361,10 @@ def test_evaluate_condition_one_of_int():
348361

349362
def test_evaluate_condition_one_of_boolean():
350363
one_of_condition_boolean = Condition(
351-
operator=OperatorType.ONE_OF, value=[True, False], attribute="status"
364+
operator=OperatorType.ONE_OF, value=["true", "false"], attribute="status"
352365
)
353366
assert evaluate_condition(one_of_condition_boolean, {"status": False})
354-
assert evaluate_condition(one_of_condition_boolean, {"status": "False"})
367+
assert evaluate_condition(one_of_condition_boolean, {"status": "false"})
355368
assert not evaluate_condition(one_of_condition_boolean, {"status": "Maybe"})
356369
assert not evaluate_condition(one_of_condition_boolean, {"status": 0})
357370
assert not evaluate_condition(one_of_condition_boolean, {"status": 1})
@@ -391,3 +404,26 @@ def test_is_not_null_operator():
391404
assert not evaluate_condition(is_not_null_condition, {"size": None})
392405
assert evaluate_condition(is_not_null_condition, {"size": 10})
393406
assert not evaluate_condition(is_not_null_condition, {})
407+
408+
409+
def test_to_string_string():
410+
assert to_string("test") == "test"
411+
412+
413+
def test_to_string_int():
414+
assert to_string(10) == "10"
415+
416+
417+
def test_to_string_float():
418+
assert to_string(10.5) == "10.5"
419+
assert to_string(10.0) == "10"
420+
assert to_string(123456789.0) == "123456789"
421+
422+
423+
def test_to_string_bool():
424+
assert to_string(True) == "true"
425+
assert to_string(False) == "false"
426+
427+
428+
def test_to_string_null():
429+
assert to_string(None) == "null"

0 commit comments

Comments
 (0)