From b5c4023480c302b0e4468200e927974fbfedfe20 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 21 Jul 2025 20:46:17 +0100 Subject: [PATCH 1/3] bump engine --- api/poetry.lock | 7 +++---- api/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index d067de4178b9..bdd5c7de9813 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1960,13 +1960,13 @@ test-tools = ["pyfakefs (>=5,<6)", "pytest-django (>=4,<5)"] [[package]] name = "flagsmith-flag-engine" -version = "5.3.0" +version = "5.4.3" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main", "dev", "split-testing", "workflows"] files = [ - {file = "flagsmith-flag-engine-5.3.0.tar.gz", hash = "sha256:87007f6a312cf11b2c201acd54b30f17de8aa039c3c56af431f1ed3c743fa84c"}, + {file = "flagsmith-flag-engine-5.4.3.tar.gz", hash = "sha256:a11b9e46c5dcfc1f45f8829864c90a4a3bd032b1a18d987ee9a33a0e5f3ef615"}, ] [package.dependencies] @@ -4242,7 +4242,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -5405,4 +5404,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">3.11,<3.13" -content-hash = "6316753c1b1b6ef97aa160d885eb1c6e8b78eca9570e69c7e67298bf95151aa5" +content-hash = "6d970c48da584e098b1bca7a97e4db1f98ae710a56d0e051437522dc0512f8d4" diff --git a/api/pyproject.toml b/api/pyproject.toml index 27e93548eecf..ce92f70de0ec 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -133,7 +133,7 @@ environs = "^14.1.1" django-lifecycle = "~1.2.4" drf-writable-nested = "~0.6.2" django-filter = "~2.4.0" -flagsmith-flag-engine = "^5.3.0" +flagsmith-flag-engine = "^5.4.3" boto3 = "~1.35.95" slack-sdk = "~3.9.0" asgiref = "~3.8.1" From 80b323bdeeb308a48be2e970d025da662e8d1eb1 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 21 Jul 2025 20:49:20 +0100 Subject: [PATCH 2/3] adapt to 5.4.3 --- .../dynamodb/wrappers/identity_wrapper.py | 10 +++++++-- api/environments/identities/models.py | 12 +++++++--- api/integrations/webhook/serializers.py | 13 +++++++---- api/util/mappers/engine.py | 22 +++++++++++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/api/environments/dynamodb/wrappers/identity_wrapper.py b/api/environments/dynamodb/wrappers/identity_wrapper.py index 56622aab5d0d..c22d9f630216 100644 --- a/api/environments/dynamodb/wrappers/identity_wrapper.py +++ b/api/environments/dynamodb/wrappers/identity_wrapper.py @@ -7,9 +7,10 @@ from boto3.dynamodb.conditions import Attr, Key from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from flag_engine.context.mappers import map_environment_identity_to_context from flag_engine.environments.models import EnvironmentModel from flag_engine.identities.models import IdentityModel -from flag_engine.segments.evaluator import get_identity_segments +from flag_engine.segments.evaluator import get_context_segments from rest_framework.exceptions import NotFound from edge_api.identities.search import EdgeIdentitySearchData @@ -189,7 +190,12 @@ def get_segment_ids( environment = EnvironmentModel.model_validate( environment_wrapper.get_item(identity.environment_api_key) ) - segments = get_identity_segments(environment, identity) + context = map_environment_identity_to_context( + environment=environment, + identity=identity, + override_traits=None, + ) + segments = get_context_segments(context, environment.project.segments) return [segment.id for segment in segments] return [] diff --git a/api/environments/identities/models.py b/api/environments/identities/models.py index 66aeb18c2945..a59ea7cf84d9 100644 --- a/api/environments/identities/models.py +++ b/api/environments/identities/models.py @@ -3,7 +3,8 @@ from django.db import models from django.db.models import Prefetch, Q -from flag_engine.segments.evaluator import evaluate_identity_in_segment +from flag_engine.context.mappers import map_environment_identity_to_context +from flag_engine.segments.evaluator import is_context_in_segment from environments.identities.managers import IdentityManager from environments.identities.traits.models import Trait @@ -170,10 +171,15 @@ def get_segments( for segment in all_segments: engine_segment = map_segment_to_engine(segment) - if evaluate_identity_in_segment( + context = map_environment_identity_to_context( + environment=self.environment, identity=engine_identity, - segment=engine_segment, override_traits=engine_traits, + ) + + if is_context_in_segment( + context=context, + segment=engine_segment, ): matching_segments.append(segment) diff --git a/api/integrations/webhook/serializers.py b/api/integrations/webhook/serializers.py index c0a0472b4939..2f07dbf992ab 100644 --- a/api/integrations/webhook/serializers.py +++ b/api/integrations/webhook/serializers.py @@ -1,7 +1,7 @@ import typing from django.db.models import Q -from flag_engine.segments.evaluator import evaluate_identity_in_segment +from flag_engine.segments.evaluator import is_context_in_segment from rest_framework import serializers from features.serializers import FeatureStateSerializerFull @@ -9,7 +9,11 @@ BaseEnvironmentIntegrationModelSerializer, ) from segments.models import Segment -from util.mappers.engine import map_identity_to_engine, map_segment_to_engine +from util.mappers.engine import ( + map_engine_identity_to_context, + map_identity_to_engine, + map_segment_to_engine, +) from .models import WebhookConfiguration @@ -33,8 +37,9 @@ def get_member(self, obj: Segment) -> bool: with_overrides=False, ) engine_segment = map_segment_to_engine(obj) - return evaluate_identity_in_segment( - identity=engine_identity, + context = map_engine_identity_to_context(engine_identity) + return is_context_in_segment( + context=context, segment=engine_segment, ) diff --git a/api/util/mappers/engine.py b/api/util/mappers/engine.py index dde78c4ded31..c97f83373fa8 100644 --- a/api/util/mappers/engine.py +++ b/api/util/mappers/engine.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional from uuid import UUID +from flag_engine.context.types import EvaluationContext from flag_engine.environments.integrations.models import IntegrationModel from flag_engine.environments.models import ( EnvironmentAPIKeyModel, @@ -418,6 +419,27 @@ def map_identity_to_engine( ) +def map_engine_identity_to_context( + identity: IdentityModel, +) -> "EvaluationContext": + """ + A special mapper to produce a minimal EvaluationContext + in an environment-less form. + Used when an environment object is not available, + like when evaluating segments in UI, which happens at the project level. + """ + return { + "environment": {"key": identity.environment_api_key, "name": ""}, + "identity": { + "identifier": identity.identifier, + "key": str(identity.django_id or identity.composite_key), + "traits": { + trait.trait_key: trait.trait_value for trait in identity.identity_traits + }, + }, + } + + def _get_prioritised_feature_states( feature_states: Iterable["FeatureState"], ) -> List["FeatureState"]: From e692a89c5227a54370127f25f468e4af4c07d60a Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 22 Jul 2025 11:50:32 +0100 Subject: [PATCH 3/3] update docs --- api/util/mappers/engine.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/util/mappers/engine.py b/api/util/mappers/engine.py index c97f83373fa8..a8ced81fbdb3 100644 --- a/api/util/mappers/engine.py +++ b/api/util/mappers/engine.py @@ -425,8 +425,7 @@ def map_engine_identity_to_context( """ A special mapper to produce a minimal EvaluationContext in an environment-less form. - Used when an environment object is not available, - like when evaluating segments in UI, which happens at the project level. + Used when an environment object is not available, like when evaluating segments for webhooks. """ return { "environment": {"key": identity.environment_api_key, "name": ""},