Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/workflows/run-test-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
with:
sdks-to-test: python
sdk-github-sha: ${{github.event.pull_request.head.sha}}
sdk-capabilities: '["cloud", "edgeDB", "clientCustomData","v2Config", "allVariables", "allFeatures", "evalReason", "cloudEvalReason"]'
sdk-capabilities: '["cloud", "edgeDB", "clientCustomData","v2Config", "allVariables", "allFeatures", "evalReason", "cloudEvalReason", "eventsEvalReason"]'
Binary file modified devcycle_python_sdk/bucketing-lib.release.wasm
Binary file not shown.
5 changes: 4 additions & 1 deletion devcycle_python_sdk/local_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ def variable(self, user: DevCycleUser, key: str, default_value: Any) -> Variable
try:
self.event_queue_manager.queue_aggregate_event(
event=DevCycleEvent(
type=EventType.AggVariableDefaulted, target=key, value=1
type=EventType.AggVariableDefaulted,
target=key,
value=1,
metaData={"evalReason": EvalReasons.DEFAULT},
),
bucketed_config=None,
)
Expand Down
16 changes: 16 additions & 0 deletions devcycle_python_sdk/models/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,19 @@ def create_default_variable(
isDefaulted=True,
eval=eval_reason,
)

def get_flag_meta_data(self) -> dict:
"""
Returns metadata dictionary for OpenFeature flag resolution.

Returns:
dict: Dictionary containing evalReasonDetails and evalReasonTargetId
if they exist, empty dict otherwise.
"""
meta_data = {}
if self.eval:
if self.eval.details:
meta_data["evalReasonDetails"] = self.eval.details
if self.eval.target_id:
meta_data["evalReasonTargetId"] = self.eval.target_id
return meta_data
15 changes: 10 additions & 5 deletions devcycle_python_sdk/open_feature_provider/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,18 @@ def _resolve(
reason=Reason.DEFAULT,
)
else:
# TODO: once eval enabled from cloud bucketing, eval reason won't be null unless defaulted
if variable.eval and variable.eval.reason:
reason = variable.eval.reason
elif variable.isDefaulted:
reason = Reason.DEFAULT
else:
reason = Reason.TARGETING_MATCH

return FlagResolutionDetails(
value=variable.value,
reason=(
Reason.DEFAULT
if variable.isDefaulted
else Reason.TARGETING_MATCH
),
reason=reason,
flag_metadata=variable.get_flag_meta_data(),
)
except ValueError as e:
# occurs if the key or default value is None
Expand Down
81 changes: 79 additions & 2 deletions test/openfeature_test/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
)

from devcycle_python_sdk.models.variable import Variable, TypeEnum
from devcycle_python_sdk.models.eval_reason import (
EvalReason,
DefaultReasonDetails,
)

from devcycle_python_sdk.open_feature_provider.provider import (
DevCycleProvider,
Expand Down Expand Up @@ -69,14 +73,19 @@ def test_resolve_details_client_returns_none(self):

def test_resolve_details_client_returns_default_variable(self):
self.client.variable.return_value = Variable.create_default_variable(
key="test-flag", default_value=False
key="test-flag",
default_value=False,
default_reason_detail=DefaultReasonDetails.USER_NOT_TARGETED,
)
context = EvaluationContext(targeting_key="user-1234")
details = self.provider._resolve("test-flag", False, context)

self.assertIsNotNone(details)
self.assertEqual(details.value, False)
self.assertEqual(details.reason, Reason.DEFAULT)
self.assertEqual(
details.flag_metadata["evalReasonDetails"], "User Not Targeted"
)

def test_resolve_boolean_details(self):
key = "test-flag"
Expand All @@ -90,6 +99,9 @@ def test_resolve_boolean_details(self):
type=TypeEnum.BOOLEAN,
isDefaulted=False,
defaultValue=False,
eval=EvalReason(
reason="TARGETING_MATCH", details="All Users", target_id="targetId"
),
)

context = EvaluationContext(targeting_key="user-1234")
Expand All @@ -98,6 +110,9 @@ def test_resolve_boolean_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, variable_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")

def test_resolve_string_details(self):
key = "test-flag"
Expand All @@ -111,6 +126,9 @@ def test_resolve_string_details(self):
type=TypeEnum.STRING,
isDefaulted=False,
defaultValue=False,
eval=EvalReason(
reason="TARGETING_MATCH", details="All Users", target_id="targetId"
),
)

context = EvaluationContext(targeting_key="user-1234")
Expand All @@ -120,6 +138,8 @@ def test_resolve_string_details(self):
self.assertEqual(details.value, variable_value)
self.assertIsInstance(details.value, str)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")

def test_resolve_integer_details(self):
key = "test-flag"
Expand All @@ -133,6 +153,9 @@ def test_resolve_integer_details(self):
type=TypeEnum.STRING,
isDefaulted=False,
defaultValue=False,
eval=EvalReason(
reason="TARGETING_MATCH", details="All Users", target_id="targetId"
),
)

context = EvaluationContext(targeting_key="user-1234")
Expand All @@ -142,6 +165,8 @@ def test_resolve_integer_details(self):
self.assertIsInstance(details.value, int)
self.assertEqual(details.value, variable_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")

def test_resolve_float_details(self):
key = "test-flag"
Expand All @@ -155,6 +180,7 @@ def test_resolve_float_details(self):
type=TypeEnum.STRING,
isDefaulted=False,
defaultValue=False,
eval=EvalReason(reason="SPLIT", details="Rollout", target_id="targetId"),
)

context = EvaluationContext(targeting_key="user-1234")
Expand All @@ -163,7 +189,9 @@ def test_resolve_float_details(self):
self.assertIsNotNone(details)
self.assertIsInstance(details.value, float)
self.assertEqual(details.value, variable_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertEqual(details.reason, Reason.SPLIT)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "Rollout")
self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")

def test_resolve_object_details_verify_default_value(self):
key = "test-flag"
Expand Down Expand Up @@ -204,6 +232,9 @@ def test_resolve_object_details(self):
type=TypeEnum.STRING,
isDefaulted=False,
defaultValue=False,
eval=EvalReason(
reason="TARGETING_MATCH", details="Rollout", target_id="targetId"
),
)

context = EvaluationContext(targeting_key="user-1234")
Expand All @@ -213,6 +244,52 @@ def test_resolve_object_details(self):
self.assertIsInstance(details.value, dict)
self.assertDictEqual(details.value, variable_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "Rollout")
self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")

def test_resolve_string_details_null_eval(self):
key = "test-flag"
variable_value = "some string"
default_value = "default string"

self.client.variable.return_value = Variable(
_id=None,
value=variable_value,
key=key,
type=TypeEnum.STRING,
isDefaulted=False,
defaultValue=False,
)

context = EvaluationContext(targeting_key="user-1234")
details = self.provider.resolve_string_details(key, default_value, context)

self.assertIsNotNone(details)
self.assertEqual(details.value, variable_value)
self.assertIsInstance(details.value, str)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)

def test_default_string_details_null_eval(self):
key = "test-flag"
default_value = "default string"

self.client.variable.return_value = Variable(
_id=None,
value=default_value,
key=key,
type=TypeEnum.STRING,
isDefaulted=True,
defaultValue=False,
)

context = EvaluationContext(targeting_key="user-1234")
details = self.provider.resolve_string_details(key, default_value, context)

self.assertIsNotNone(details)
self.assertEqual(details.value, default_value)
self.assertIsInstance(details.value, str)
self.assertEqual(details.reason, Reason.DEFAULT)


if __name__ == "__main__":
Expand Down
25 changes: 25 additions & 0 deletions test/openfeature_test/test_provider_local_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ def test_resolve_boolean_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, expected_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(
details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
)

@responses.activate
def test_resolve_integer_details(self):
Expand All @@ -82,6 +87,11 @@ def test_resolve_integer_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, expected_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(
details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
)

@responses.activate
def test_resolve_float_details(self):
Expand All @@ -98,6 +108,11 @@ def test_resolve_float_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, expected_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(
details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
)

@responses.activate
def test_resolve_string_details(self):
Expand All @@ -114,6 +129,11 @@ def test_resolve_string_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, expected_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(
details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
)

@responses.activate
def test_resolve_object_details(self):
Expand All @@ -134,3 +154,8 @@ def test_resolve_object_details(self):
self.assertIsNotNone(details)
self.assertEqual(details.value, expected_value)
self.assertEqual(details.reason, Reason.TARGETING_MATCH)
self.assertIsNotNone(details.flag_metadata)
self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
self.assertEqual(
details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
)
2 changes: 1 addition & 1 deletion update_wasm_lib.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

BUCKETING_LIB_VERSION="1.40.2"
BUCKETING_LIB_VERSION="1.41.0"

if [[ -n "$1" ]]; then
BUCKETING_LIB_VERSION="$1"
Expand Down
Loading