From 407209c8a5638b9b4958b651ff3853f9e308b3c0 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 24 Oct 2025 17:02:56 +0100 Subject: [PATCH 1/3] feat: Support implicit `IdentityContext.key` --- .gitmodules | 2 +- flag_engine/segments/evaluator.py | 36 +++++++++++++++++++---------- tests/engine_tests/engine-test-data | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.gitmodules b/.gitmodules index 7aa2113..266f2f5 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 - tag = v3.0.0 + branch = feat/implicit-identity-key diff --git a/flag_engine/segments/evaluator.py b/flag_engine/segments/evaluator.py index aee805b..d7b4536 100644 --- a/flag_engine/segments/evaluator.py +++ b/flag_engine/segments/evaluator.py @@ -41,6 +41,11 @@ class FeatureContextWithSegmentName(TypedDict, typing.Generic[FeatureMetadataT]) segment_name: str +# Type alias for EvaluationContext with any metadata types +# used in internal evaluation logic +_EvaluationContextAnyMeta = EvaluationContext[typing.Any, typing.Any] + + def get_evaluation_result( context: EvaluationContext[SegmentMetadataT, FeatureMetadataT], ) -> EvaluationResult[SegmentMetadataT, FeatureMetadataT]: @@ -90,11 +95,7 @@ def get_evaluation_result( ) ) - identity_key = ( - identity_context["key"] - if (identity_context := context.get("identity")) - else None - ) + identity_key = _get_identity_key(context) for feature_context in (context.get("features") or {}).values(): feature_name = feature_context["name"] if feature_context_with_segment_name := segment_feature_contexts.get( @@ -174,7 +175,7 @@ def get_flag_result_from_feature_context( def is_context_in_segment( - context: EvaluationContext[typing.Any, typing.Any], + context: _EvaluationContextAnyMeta, segment_context: SegmentContext[typing.Any, typing.Any], ) -> bool: return bool(rules := segment_context["rules"]) and all( @@ -186,7 +187,7 @@ def is_context_in_segment( def context_matches_rule( - context: EvaluationContext[typing.Any, typing.Any], + context: _EvaluationContextAnyMeta, rule: SegmentRule, segment_key: SupportsStr, ) -> bool: @@ -216,7 +217,7 @@ def context_matches_rule( def context_matches_condition( - context: EvaluationContext[typing.Any, typing.Any], + context: _EvaluationContextAnyMeta, condition: SegmentCondition, segment_key: SupportsStr, ) -> bool: @@ -277,7 +278,7 @@ def context_matches_condition( def get_context_value( - context: EvaluationContext[typing.Any, typing.Any], + context: _EvaluationContextAnyMeta, property: str, ) -> ContextValue: value = None @@ -371,8 +372,19 @@ def inner( } +def _get_identity_key( + context: _EvaluationContextAnyMeta, +) -> typing.Optional[SupportsStr]: + if identity_context := context.get("identity"): + return ( + identity_context.get("key") + or f"{context['environment']['key']}_{identity_context['identifier']}" + ) + return None + + def _get_trait_value( - context: EvaluationContext[SegmentMetadataT], + context: _EvaluationContextAnyMeta, trait_key: str, ) -> ContextValue: if identity_context := context.get("identity"): @@ -384,7 +396,7 @@ def _get_trait_value( @lru_cache def _get_context_value_getter( property: str, -) -> typing.Callable[[EvaluationContext[SegmentMetadataT]], ContextValue]: +) -> typing.Callable[[_EvaluationContextAnyMeta], ContextValue]: """ Get a function to retrieve a context value based on property value, assumed to be either a JSONPath string or a trait key. @@ -399,7 +411,7 @@ def _get_context_value_getter( # but not a valid JSONPath, is used. return partial(_get_trait_value, trait_key=property) - def getter(context: EvaluationContext[SegmentMetadataT]) -> ContextValue: + def getter(context: _EvaluationContextAnyMeta) -> ContextValue: value: object if (value := _get_trait_value(context, property)) is not None: return value diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index 8d19e96..51cb2f5 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit 8d19e9627013c0e0213c29f3318fd45a179868fa +Subproject commit 51cb2f5df9951da70cbe62da876cce4a205cc017 From c4845d24c7b313062379c44580312cb3d5cf9da1 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 24 Oct 2025 17:04:26 +0100 Subject: [PATCH 2/3] clarify typing --- flag_engine/context/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag_engine/context/types.py b/flag_engine/context/types.py index d5e6824..7e26142 100644 --- a/flag_engine/context/types.py +++ b/flag_engine/context/types.py @@ -30,7 +30,7 @@ class FeatureValue(TypedDict): class IdentityContext(TypedDict): identifier: str - key: str + key: NotRequired[str] traits: NotRequired[Dict[str, ContextValue]] From 277403c72d30b6840eb66f556750df85e9924130 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 24 Oct 2025 17:22:01 +0100 Subject: [PATCH 3/3] bump engine-test-data --- .gitmodules | 2 +- tests/engine_tests/engine-test-data | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 266f2f5..41aa8d9 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 = feat/implicit-identity-key + tag = v3.1.0 diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index 51cb2f5..6ab57ec 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit 51cb2f5df9951da70cbe62da876cce4a205cc017 +Subproject commit 6ab57ec67bc84659e8b5aa41534b04fe45cc4cbe