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
3 changes: 3 additions & 0 deletions flagsmith/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Flag(BaseFlag):
feature_id: int
feature_name: str
variant: typing.Optional[str] = None
reason: typing.Optional[str] = None
is_default: bool = field(default=False)

@classmethod
Expand All @@ -66,6 +67,7 @@ def from_evaluation_result(
feature_name=flag_result["name"],
feature_id=metadata["id"],
variant=flag_result.get("variant"),
reason=flag_result["reason"],
)
raise ValueError(
"FlagResult metadata is missing. Cannot create Flag instance. "
Expand All @@ -80,6 +82,7 @@ def from_api_flag(cls, flag_data: typing.Mapping[str, typing.Any]) -> Flag:
feature_name=flag_data["feature"]["name"],
feature_id=flag_data["feature"]["id"],
variant=flag_data.get("variant"),
reason=flag_data.get("reason"),
)


Expand Down
1 change: 1 addition & 0 deletions tests/data/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"project": 1
},
"feature_state_value": "some-value",
"reason": "DEFAULT",
"enabled": true,
"environment": 1,
"identity": null,
Expand Down
1 change: 1 addition & 0 deletions tests/data/identities.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"project": 1
},
"feature_state_value": "some-value",
"reason": "SPLIT; weight=50.0",
"variant": "treatment",
"enabled": true,
"environment": 1,
Expand Down
5 changes: 5 additions & 0 deletions tests/test_flagsmith.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def test_get_environment_flags_calls_api_when_no_local_environment(
assert all_flags[0].enabled is True
assert all_flags[0].value == "some-value"
assert all_flags[0].feature_name == "some_feature"
assert all_flags[0].reason == "DEFAULT"


@responses.activate()
Expand Down Expand Up @@ -114,6 +115,7 @@ def test_get_environment_flags_omits_segments_from_evaluation_context(
"name": "some_feature",
"enabled": True,
"value": "some-feature-state-value",
"reason": "DEFAULT",
"metadata": {"id": 1},
}
},
Expand Down Expand Up @@ -154,6 +156,7 @@ def test_get_identity_flags_calls_api_when_no_local_environment_no_traits(
assert identity_flags[0].enabled is True
assert identity_flags[0].value == "some-value"
assert identity_flags[0].feature_name == "some_feature"
assert identity_flags[0].reason == "SPLIT; weight=50.0"


@responses.activate()
Expand Down Expand Up @@ -208,6 +211,7 @@ def test_get_identity_flags_uses_local_environment_when_available(
"name": "some_feature",
"enabled": True,
"value": "some-feature-state-value",
"reason": "DEFAULT",
"metadata": {"id": 1},
}
},
Expand Down Expand Up @@ -250,6 +254,7 @@ def test_get_identity_flags_includes_segments_in_evaluation_context(
"name": "some_feature",
"enabled": True,
"value": "some-feature-state-value",
"reason": "DEFAULT",
"metadata": {"id": 1},
}
},
Expand Down
36 changes: 36 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def test_flag_from_evaluation_result() -> None:
assert flag.feature_id == 123
assert flag.is_default is False
assert flag.variant == "control"
assert flag.reason == "DEFAULT"


def test_flag_from_evaluation_result__no_variant__is_none() -> None:
Expand Down Expand Up @@ -218,6 +219,37 @@ def test_flag_from_api_flag__no_variant__is_none() -> None:
assert flag.variant is None


def test_flag_from_api_flag__sets_reason() -> None:
# Given
flag_data = {
"enabled": True,
"feature_state_value": "test-value",
"feature": {"name": "test_feature", "id": 123},
"reason": "TARGETING_MATCH; segment=premium",
}

# When
flag = Flag.from_api_flag(flag_data)

# Then
assert flag.reason == "TARGETING_MATCH; segment=premium"


def test_flag_from_api_flag__no_reason__is_none() -> None:
# Given
flag_data = {
"enabled": True,
"feature_state_value": "test-value",
"feature": {"name": "test_feature", "id": 123},
}

# When
flag = Flag.from_api_flag(flag_data)

# Then
assert flag.reason is None


def test_get_flag_without_pipeline_processor() -> None:
flags = Flags(
flags={
Expand Down Expand Up @@ -335,8 +367,10 @@ def test_lazy_flags__get_flag__applies_matching_segment_override(
# When: we read the targeted feature.
target = lazy_flags.get_flag("target")
# Then: the override wins over the base feature value.
assert isinstance(target, Flag)
assert target.enabled is True
assert target.value == "premium-value"
assert target.reason == "TARGETING_MATCH; segment=premium_segment"


def test_lazy_flags__get_flag__skips_non_matching_segment_override(
Expand All @@ -355,8 +389,10 @@ def test_lazy_flags__get_flag__skips_non_matching_segment_override(
target = flags.get_flag("target")

# Then: the override doesn't win and base-value comes through.
assert isinstance(target, Flag)
assert target.enabled is False
assert target.value == "base-value"
assert target.reason == "DEFAULT"


def test_lazy_flags__get_flag__caches_per_feature(
Expand Down
Loading