Skip to content

Commit 68d44a1

Browse files
authored
fix: get_identity_segments tries to return identity override segments (#159)
1 parent 1c7e8c9 commit 68d44a1

File tree

11 files changed

+159
-74
lines changed

11 files changed

+159
-74
lines changed

flagsmith/flagsmith.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
map_context_and_identity_data_to_context,
1515
map_environment_document_to_context,
1616
map_environment_document_to_environment_updated_at,
17+
map_segment_results_to_identity_segments,
1718
)
1819
from flagsmith.models import DefaultFlag, Flags, Segment
1920
from flagsmith.offline_handlers import OfflineHandler
@@ -22,6 +23,7 @@
2223
from flagsmith.types import (
2324
ApplicationMetadata,
2425
JsonType,
26+
SDKEvaluationContext,
2527
StreamEvent,
2628
TraitMapping,
2729
)
@@ -106,7 +108,7 @@ def __init__(
106108
self.default_flag_handler = default_flag_handler
107109
self.enable_realtime_updates = enable_realtime_updates
108110
self._analytics_processor: typing.Optional[AnalyticsProcessor] = None
109-
self._evaluation_context: typing.Optional[engine.EvaluationContext] = None
111+
self._evaluation_context: typing.Optional[SDKEvaluationContext] = None
110112
self._environment_updated_at: typing.Optional[datetime] = None
111113

112114
# argument validation
@@ -283,10 +285,8 @@ def get_identity_segments(
283285
evaluation_result = engine.get_evaluation_result(
284286
context=context,
285287
)
286-
return [
287-
Segment(id=int(segment_result["key"]), name=segment_result["name"])
288-
for segment_result in evaluation_result["segments"]
289-
]
288+
289+
return map_segment_results_to_identity_segments(evaluation_result["segments"])
290290

291291
def update_environment(self) -> None:
292292
try:

flagsmith/mappers.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66

77
import sseclient
88
from flag_engine.context.types import (
9-
EvaluationContext,
109
FeatureContext,
1110
SegmentContext,
1211
SegmentRule,
1312
)
13+
from flag_engine.result.types import SegmentResult
1414
from flag_engine.segments.types import ContextValue
1515

16-
from flagsmith.types import StreamEvent, TraitConfig
16+
from flagsmith.models import Segment
17+
from flagsmith.types import (
18+
SDKEvaluationContext,
19+
SegmentMetadata,
20+
StreamEvent,
21+
TraitConfig,
22+
)
1723

1824
OverrideKey = typing.Tuple[
1925
str,
@@ -24,6 +30,24 @@
2430
OverridesKey = typing.Tuple[OverrideKey, ...]
2531

2632

33+
def map_segment_results_to_identity_segments(
34+
segment_results: list[SegmentResult[SegmentMetadata]],
35+
) -> list[Segment]:
36+
identity_segments: list[Segment] = []
37+
for segment_result in segment_results:
38+
if metadata := segment_result.get("metadata"):
39+
if metadata.get("source") == "api" and (
40+
(flagsmith_id := metadata.get("flagsmith_id")) is not None
41+
):
42+
identity_segments.append(
43+
Segment(
44+
id=flagsmith_id,
45+
name=segment_result["name"],
46+
)
47+
)
48+
return identity_segments
49+
50+
2751
def map_sse_event_to_stream_event(event: sseclient.Event) -> StreamEvent:
2852
event_data = json.loads(event.data)
2953
return {
@@ -45,7 +69,7 @@ def map_environment_document_to_environment_updated_at(
4569

4670

4771
def map_context_and_identity_data_to_context(
48-
context: EvaluationContext,
72+
context: SDKEvaluationContext,
4973
identifier: str,
5074
traits: typing.Optional[
5175
typing.Mapping[
@@ -56,7 +80,7 @@ def map_context_and_identity_data_to_context(
5680
],
5781
]
5882
],
59-
) -> EvaluationContext:
83+
) -> SDKEvaluationContext:
6084
return {
6185
**context,
6286
"identity": {
@@ -76,7 +100,7 @@ def map_context_and_identity_data_to_context(
76100

77101
def map_environment_document_to_context(
78102
environment_document: dict[str, typing.Any],
79-
) -> EvaluationContext:
103+
) -> SDKEvaluationContext:
80104
return {
81105
"environment": {
82106
"key": environment_document["api_key"],
@@ -90,7 +114,7 @@ def map_environment_document_to_context(
90114
},
91115
"segments": {
92116
**{
93-
(segment_key := str(segment["id"])): {
117+
(segment_key := str(segment_id := segment["id"])): {
94118
"key": segment_key,
95119
"name": segment["name"],
96120
"rules": _map_environment_document_rules_to_context_rules(
@@ -101,6 +125,10 @@ def map_environment_document_to_context(
101125
segment.get("feature_states") or []
102126
)
103127
),
128+
"metadata": SegmentMetadata(
129+
flagsmith_id=segment_id,
130+
source="api",
131+
),
104132
}
105133
for segment in environment_document["project"]["segments"]
106134
},
@@ -113,7 +141,7 @@ def map_environment_document_to_context(
113141

114142
def _map_identity_overrides_to_segments(
115143
identity_overrides: list[dict[str, typing.Any]],
116-
) -> dict[str, SegmentContext]:
144+
) -> dict[str, SegmentContext[SegmentMetadata]]:
117145
features_to_identifiers: typing.Dict[
118146
OverridesKey,
119147
typing.List[str],
@@ -137,7 +165,7 @@ def _map_identity_overrides_to_segments(
137165
)
138166
)
139167
features_to_identifiers[overrides_key].append(identity_override["identifier"])
140-
segment_contexts: typing.Dict[str, SegmentContext] = {}
168+
segment_contexts: typing.Dict[str, SegmentContext[SegmentMetadata]] = {}
141169
for overrides_key, identifiers in features_to_identifiers.items():
142170
# Create a segment context for each unique set of overrides
143171
# Generate a unique key to avoid collisions
@@ -168,6 +196,7 @@ def _map_identity_overrides_to_segments(
168196
}
169197
for feature_key, feature_name, feature_enabled, feature_value in overrides_key
170198
],
199+
metadata=SegmentMetadata(source="identity_overrides"),
171200
)
172201
return segment_contexts
173202

flagsmith/models.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import typing
44
from dataclasses import dataclass, field
55

6-
from flag_engine.result.types import EvaluationResult, FlagResult
6+
from flag_engine.result.types import FlagResult
77

88
from flagsmith.analytics import AnalyticsProcessor
99
from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError
10+
from flagsmith.types import SDKEvaluationResult
1011

1112

1213
@dataclass
@@ -57,19 +58,19 @@ class Flags:
5758
@classmethod
5859
def from_evaluation_result(
5960
cls,
60-
evaluation_result: EvaluationResult,
61+
evaluation_result: SDKEvaluationResult,
6162
analytics_processor: typing.Optional[AnalyticsProcessor],
6263
default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]],
6364
) -> Flags:
6465
return cls(
6566
flags={
66-
flag["name"]: Flag(
67+
flag_name: Flag(
6768
enabled=flag["enabled"],
6869
value=flag["value"],
6970
feature_name=flag["name"],
7071
feature_id=int(flag["feature_key"]),
7172
)
72-
for flag in evaluation_result["flags"]
73+
for flag_name, flag in evaluation_result["flags"].items()
7374
},
7475
default_flag_handler=default_flag_handler,
7576
_analytics_processor=analytics_processor,

flagsmith/offline_handlers.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
from pathlib import Path
33
from typing import Protocol
44

5-
from flag_engine.context.types import EvaluationContext
6-
75
from flagsmith.mappers import map_environment_document_to_context
6+
from flagsmith.types import SDKEvaluationContext
87

98

109
class OfflineHandler(Protocol):
11-
def get_evaluation_context(self) -> EvaluationContext: ...
10+
def get_evaluation_context(self) -> SDKEvaluationContext: ...
1211

1312

1413
class EvaluationContextLocalFileHandler:
@@ -21,11 +20,11 @@ class EvaluationContextLocalFileHandler:
2120
"""
2221

2322
def __init__(self, file_path: str) -> None:
24-
self.evaluation_context: EvaluationContext = json.loads(
23+
self.evaluation_context: SDKEvaluationContext = json.loads(
2524
Path(file_path).read_text(),
2625
)
2726

28-
def get_evaluation_context(self) -> EvaluationContext:
27+
def get_evaluation_context(self) -> SDKEvaluationContext:
2928
return self.evaluation_context
3029

3130

@@ -39,15 +38,15 @@ class EnvironmentDocumentLocalFileHandler:
3938
"""
4039

4140
def __init__(self, file_path: str) -> None:
42-
self.evaluation_context: EvaluationContext = (
41+
self.evaluation_context: SDKEvaluationContext = (
4342
map_environment_document_to_context(
4443
json.loads(
4544
Path(file_path).read_text(),
4645
),
4746
)
4847
)
4948

50-
def get_evaluation_context(self) -> EvaluationContext:
49+
def get_evaluation_context(self) -> SDKEvaluationContext:
5150
return self.evaluation_context
5251

5352

flagsmith/types.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import typing
22
from datetime import datetime
33

4+
from flag_engine.context.types import EvaluationContext
45
from flag_engine.engine import ContextValue
6+
from flag_engine.result.types import EvaluationResult
57
from typing_extensions import NotRequired, TypeAlias
68

79
_JsonScalarType: TypeAlias = typing.Union[
@@ -33,3 +35,14 @@ class TraitConfig(typing.TypedDict):
3335
class ApplicationMetadata(typing.TypedDict):
3436
name: NotRequired[str]
3537
version: NotRequired[str]
38+
39+
40+
class SegmentMetadata(typing.TypedDict):
41+
flagsmith_id: NotRequired[int]
42+
"""The ID of the segment used in Flagsmith API."""
43+
source: NotRequired[typing.Literal["api", "identity_overrides"]]
44+
"""The source of the segment, e.g. 'api', 'identity_overrides'."""
45+
46+
47+
SDKEvaluationContext = EvaluationContext[SegmentMetadata]
48+
SDKEvaluationResult = EvaluationResult[SegmentMetadata]

poetry.lock

Lines changed: 10 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ packages = [{ include = "flagsmith" }]
1313
python = ">=3.9,<4"
1414
requests = "^2.32.3"
1515
requests-futures = "^1.0.1"
16-
flagsmith-flag-engine = "^7.0.0"
16+
flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine.git", branch = "feat/generic-metadata" }
1717
sseclient-py = "^1.8.0"
1818

1919
[tool.poetry.group.dev]

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
import pytest
99
import responses
10-
from flag_engine.engine import EvaluationContext
1110
from pyfakefs.fake_filesystem import FakeFilesystem
1211
from pytest_mock import MockerFixture
1312

1413
from flagsmith import Flagsmith
1514
from flagsmith.analytics import AnalyticsProcessor
1615
from flagsmith.mappers import map_environment_document_to_context
16+
from flagsmith.types import SDKEvaluationContext
1717

1818
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
1919

@@ -74,7 +74,7 @@ def local_eval_flagsmith(
7474

7575

7676
@pytest.fixture()
77-
def evaluation_context(environment_json: str) -> EvaluationContext:
77+
def evaluation_context(environment_json: str) -> SDKEvaluationContext:
7878
return map_environment_document_to_context(json.loads(environment_json))
7979

8080

0 commit comments

Comments
 (0)