From 95ecea71ad7bc77aafdb396c5f07946d3b99ff72 Mon Sep 17 00:00:00 2001 From: BakirFS Date: Mon, 28 Sep 2026 13:19:02 +0200 Subject: [PATCH 1/6] started work --- flag_engine/segments/evaluator.py | 33 +++++++++++++++++++++-- tests/unit/test_engine.py | 45 +++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/flag_engine/segments/evaluator.py b/flag_engine/segments/evaluator.py index 9b96efa..a1a06a4 100644 --- a/flag_engine/segments/evaluator.py +++ b/flag_engine/segments/evaluator.py @@ -213,6 +213,7 @@ def evaluate_features( return {} flags: dict[str, FlagResult[FeatureMetadataT]] = {} + targeted_feature_names = get_targeted_feature_names(context) for feature_context in features.values(): feature_name = feature_context["name"] @@ -226,12 +227,38 @@ def evaluate_features( flags[feature_name] = get_flag_result_from_context( context=context, feature_context=context["features"][feature_name], - reason="DEFAULT", + reason=get_fallback_reason(feature_name, targeted_feature_names), ) return flags +def get_targeted_feature_names( + context: _EvaluationContextAnyMeta, +) -> set[str]: + """ + Get the names of the features overridden by at least one segment. + """ + return { + override["name"] + for segment_context in (context.get("segments") or {}).values() + for override in segment_context.get("overrides") or () + } + + +def get_fallback_reason( + feature_name: str, + targeted_feature_names: typing.Container[str], +) -> str: + """ + Get the reason for a flag evaluated to its environment default. + + `DEFAULT` if the feature has targeting rules that did not match, + `STATIC` if it has none. + """ + return "DEFAULT" if feature_name in targeted_feature_names else "STATIC" + + _JSONPATH_PREFIX = "$." CIRCULAR_DEPENDENCY_REASON = "ERROR; code=CIRCULAR_DEPENDENCY" @@ -295,7 +322,9 @@ def resolve_feature(self, feature_name: str) -> None: self._flags[feature_name] = get_flag_result_from_context( context=self._context, feature_context=feature_context, - reason="DEFAULT", + reason=get_fallback_reason( + feature_name, self._segment_keys_by_feature_name + ), ) def matches_segment(self, segment_key: str) -> bool: diff --git a/tests/unit/test_engine.py b/tests/unit/test_engine.py index 54da757..f803914 100644 --- a/tests/unit/test_engine.py +++ b/tests/unit/test_engine.py @@ -24,14 +24,14 @@ def test_get_evaluation_result__no_overrides__returns_expected( "feature_1": { "enabled": True, "name": "feature_1", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -59,7 +59,7 @@ def test_get_evaluation_result__segment_override__returns_expected( "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -68,6 +68,41 @@ def test_get_evaluation_result__segment_override__returns_expected( } +def test_get_evaluation_result__unmatched_segment_override__returns_default_reason( + context: EvaluationContext, +) -> None: + # Given + context["segments"] = { + "1": { + "key": "1", + "name": "unmatched_segment", + "rules": [ + { + "type": "ALL", + "conditions": [ + {"property": "foo", "operator": "EQUAL", "value": "no match"} + ], + } + ], + "overrides": [ + { + "key": "4", + "name": "feature_1", + "enabled": False, + "value": "segment_override", + } + ], + } + } + + # When + result = get_evaluation_result(context) + + # Then + assert result["flags"]["feature_1"]["reason"] == "DEFAULT" + assert result["flags"]["feature_2"]["reason"] == "STATIC" + + def test_get_evaluation_result__identity_override__returns_expected( identity: IdentityContext, context: EvaluationContext, @@ -115,7 +150,7 @@ def test_get_evaluation_result__identity_override__returns_expected( "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -213,7 +248,7 @@ def test_get_evaluation_result__two_segments_override_same_feature__returns_expe "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, From 1549b48ec5d301369518d98a2c9c46392c9fa838 Mon Sep 17 00:00:00 2001 From: BakirFS Date: Wed, 30 Sep 2026 11:05:59 +0200 Subject: [PATCH 2/6] simplified syntax --- flag_engine/segments/evaluator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/flag_engine/segments/evaluator.py b/flag_engine/segments/evaluator.py index a1a06a4..57b7150 100644 --- a/flag_engine/segments/evaluator.py +++ b/flag_engine/segments/evaluator.py @@ -239,11 +239,15 @@ def get_targeted_feature_names( """ Get the names of the features overridden by at least one segment. """ - return { - override["name"] - for segment_context in (context.get("segments") or {}).values() - for override in segment_context.get("overrides") or () - } + targeted_feature_names: set[str] = set() + segment_contexts = context.get("segments") or {} + + for segment_context in segment_contexts.values(): + overrides = segment_context.get("overrides") or () + for override in overrides: + targeted_feature_names.add(override["name"]) + + return targeted_feature_names def get_fallback_reason( From fdf0448428ddd77886fb119bb2302ce4a43c5392 Mon Sep 17 00:00:00 2001 From: BakirFS Date: Wed, 30 Sep 2026 15:28:55 +0200 Subject: [PATCH 3/6] deps: Point engine-test-data at aceb9e1 (pending release) --- tests/engine_tests/engine-test-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index 28363b3..4318fb9 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit 28363b388de2850c7c3f1b6e7a8bdd41d8da433c +Subproject commit 4318fb9fd03ebb5381e5069765e9ca3ec32046f5 From 8c9a9fa471feb0c50e0e5d2d1b8c41a9877ad896 Mon Sep 17 00:00:00 2001 From: BakirFS Date: Wed, 7 Oct 2026 13:06:13 +0200 Subject: [PATCH 4/6] deps: Bump engine-test-data to v3.12.0 Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitmodules | 2 +- tests/engine_tests/engine-test-data | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 935bed8..4fa4237 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine_tests/engine-test-data"] path = tests/engine_tests/engine-test-data url = https://github.com/flagsmith/engine-test-data.git - branch = v3.11.0 + branch = v3.12.0 diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index 4318fb9..aceb9e1 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit 4318fb9fd03ebb5381e5069765e9ca3ec32046f5 +Subproject commit aceb9e189c9f681e935b006680987f9f1e44bbe4 From edc1d08873f77e459cea2c457aac76c4ac7967fc Mon Sep 17 00:00:00 2001 From: BakirFS Date: Wed, 7 Oct 2026 13:12:34 +0200 Subject: [PATCH 5/6] removed test --- tests/unit/test_engine.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/tests/unit/test_engine.py b/tests/unit/test_engine.py index f803914..88a0f27 100644 --- a/tests/unit/test_engine.py +++ b/tests/unit/test_engine.py @@ -68,41 +68,6 @@ def test_get_evaluation_result__segment_override__returns_expected( } -def test_get_evaluation_result__unmatched_segment_override__returns_default_reason( - context: EvaluationContext, -) -> None: - # Given - context["segments"] = { - "1": { - "key": "1", - "name": "unmatched_segment", - "rules": [ - { - "type": "ALL", - "conditions": [ - {"property": "foo", "operator": "EQUAL", "value": "no match"} - ], - } - ], - "overrides": [ - { - "key": "4", - "name": "feature_1", - "enabled": False, - "value": "segment_override", - } - ], - } - } - - # When - result = get_evaluation_result(context) - - # Then - assert result["flags"]["feature_1"]["reason"] == "DEFAULT" - assert result["flags"]["feature_2"]["reason"] == "STATIC" - - def test_get_evaluation_result__identity_override__returns_expected( identity: IdentityContext, context: EvaluationContext, From 0ae1af54fd919a0f5161690604c2ca624cfecaba Mon Sep 17 00:00:00 2001 From: BakirFS Date: Wed, 7 Oct 2026 13:45:10 +0200 Subject: [PATCH 6/6] pin engine-test-data to 3.12.1 --- .gitmodules | 2 +- tests/engine_tests/engine-test-data | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 4fa4237..e1bc1d5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine_tests/engine-test-data"] path = tests/engine_tests/engine-test-data url = https://github.com/flagsmith/engine-test-data.git - branch = v3.12.0 + branch = v3.12.1 diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index aceb9e1..cb06740 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit aceb9e189c9f681e935b006680987f9f1e44bbe4 +Subproject commit cb067401093766e55c3354c1de7c73d9331334df