Skip to content

Commit 22370a1

Browse files
committed
support generics
1 parent 6cbd7bc commit 22370a1

File tree

11 files changed

+67
-55
lines changed

11 files changed

+67
-55
lines changed

flagsmith/flagsmith.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from flagsmith.types import (
2424
ApplicationMetadata,
2525
JsonType,
26+
SDKEvaluationContext,
2627
StreamEvent,
2728
TraitMapping,
2829
)
@@ -107,7 +108,7 @@ def __init__(
107108
self.default_flag_handler = default_flag_handler
108109
self.enable_realtime_updates = enable_realtime_updates
109110
self._analytics_processor: typing.Optional[AnalyticsProcessor] = None
110-
self._evaluation_context: typing.Optional[engine.EvaluationContext] = None
111+
self._evaluation_context: typing.Optional[SDKEvaluationContext] = None
111112
self._environment_updated_at: typing.Optional[datetime] = None
112113

113114
# argument validation

flagsmith/mappers.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import sseclient
88
from flag_engine.context.types import (
9-
EvaluationContext,
109
FeatureContext,
1110
SegmentContext,
1211
SegmentRule,
@@ -15,7 +14,12 @@
1514
from flag_engine.segments.types import ContextValue
1615

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

2024
OverrideKey = typing.Tuple[
2125
str,
@@ -27,21 +31,20 @@
2731

2832

2933
def map_segment_results_to_identity_segments(
30-
segment_results: list[SegmentResult],
34+
segment_results: list[SegmentResult[SegmentMetadata]],
3135
) -> list[Segment]:
3236
identity_segments: list[Segment] = []
3337
for segment_result in segment_results:
34-
if raw_metadata := segment_result.get("metadata"):
35-
metadata = typing.cast(SegmentMetadata, raw_metadata)
36-
if metadata.get("source") == "api" and (
37-
(flagsmith_id := metadata.get("flagsmith_id")) is not None
38-
):
39-
identity_segments.append(
40-
Segment(
41-
id=flagsmith_id,
42-
name=segment_result["name"],
43-
)
38+
metadata = segment_result["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"],
4446
)
47+
)
4548
return identity_segments
4649

4750

@@ -66,7 +69,7 @@ def map_environment_document_to_environment_updated_at(
6669

6770

6871
def map_context_and_identity_data_to_context(
69-
context: EvaluationContext,
72+
context: SDKEvaluationContext,
7073
identifier: str,
7174
traits: typing.Optional[
7275
typing.Mapping[
@@ -77,7 +80,7 @@ def map_context_and_identity_data_to_context(
7780
],
7881
]
7982
],
80-
) -> EvaluationContext:
83+
) -> SDKEvaluationContext:
8184
return {
8285
**context,
8386
"identity": {
@@ -97,7 +100,7 @@ def map_context_and_identity_data_to_context(
97100

98101
def map_environment_document_to_context(
99102
environment_document: dict[str, typing.Any],
100-
) -> EvaluationContext:
103+
) -> SDKEvaluationContext:
101104
return {
102105
"environment": {
103106
"key": environment_document["api_key"],
@@ -122,11 +125,9 @@ def map_environment_document_to_context(
122125
segment.get("feature_states") or []
123126
)
124127
),
125-
"metadata": dict(
126-
SegmentMetadata(
127-
flagsmith_id=segment_id,
128-
source="api",
129-
)
128+
"metadata": SegmentMetadata(
129+
flagsmith_id=segment_id,
130+
source="api",
130131
),
131132
}
132133
for segment in environment_document["project"]["segments"]
@@ -140,7 +141,7 @@ def map_environment_document_to_context(
140141

141142
def _map_identity_overrides_to_segments(
142143
identity_overrides: list[dict[str, typing.Any]],
143-
) -> dict[str, SegmentContext]:
144+
) -> dict[str, SegmentContext[SegmentMetadata]]:
144145
features_to_identifiers: typing.Dict[
145146
OverridesKey,
146147
typing.List[str],
@@ -164,12 +165,11 @@ def _map_identity_overrides_to_segments(
164165
)
165166
)
166167
features_to_identifiers[overrides_key].append(identity_override["identifier"])
167-
segment_contexts: typing.Dict[str, SegmentContext] = {}
168+
segment_contexts: typing.Dict[str, SegmentContext[SegmentMetadata]] = {}
168169
for overrides_key, identifiers in features_to_identifiers.items():
169170
# Create a segment context for each unique set of overrides
170171
# Generate a unique key to avoid collisions
171172
segment_key = str(hash(overrides_key))
172-
segment_metadata = SegmentMetadata(source="identity_overrides")
173173
segment_contexts[segment_key] = SegmentContext(
174174
key="", # Identity override segments never use % Split operator
175175
name="identity_overrides",
@@ -196,7 +196,7 @@ def _map_identity_overrides_to_segments(
196196
}
197197
for feature_key, feature_name, feature_enabled, feature_value in overrides_key
198198
],
199-
metadata=dict(segment_metadata),
199+
metadata=SegmentMetadata(source="identity_overrides"),
200200
)
201201
return segment_contexts
202202

flagsmith/models.py

Lines changed: 3 additions & 2 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,7 +58,7 @@ 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:

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: 6 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[
@@ -40,3 +42,7 @@ class SegmentMetadata(typing.TypedDict):
4042
"""The ID of the segment used in Flagsmith API."""
4143
source: NotRequired[typing.Literal["api", "identity_overrides"]]
4244
"""The source of the segment, e.g. 'api', 'identity_overrides'."""
45+
46+
47+
SDKEvaluationContext = EvaluationContext[SegmentMetadata]
48+
SDKEvaluationResult = EvaluationResult[SegmentMetadata]

poetry.lock

Lines changed: 9 additions & 5 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 = "^8.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

tests/test_flagsmith.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import pytest
66
import requests
77
import responses
8-
from flag_engine.engine import EvaluationContext
98
from pytest_mock import MockerFixture
109
from responses import matchers
1110

@@ -16,6 +15,7 @@
1615
)
1716
from flagsmith.models import DefaultFlag, Flags
1817
from flagsmith.offline_handlers import OfflineHandler
18+
from flagsmith.types import SDKEvaluationContext
1919

2020

2121
def test_flagsmith_starts_polling_manager_on_init_if_enabled(
@@ -39,7 +39,7 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled(
3939
def test_update_environment_sets_environment(
4040
flagsmith: Flagsmith,
4141
environment_json: str,
42-
evaluation_context: EvaluationContext,
42+
evaluation_context: SDKEvaluationContext,
4343
) -> None:
4444
# Given
4545
responses.add(method="GET", url=flagsmith.environment_url, body=environment_json)
@@ -76,7 +76,7 @@ def test_get_environment_flags_calls_api_when_no_local_environment(
7676
@responses.activate()
7777
def test_get_environment_flags_uses_local_environment_when_available(
7878
flagsmith: Flagsmith,
79-
evaluation_context: EvaluationContext,
79+
evaluation_context: SDKEvaluationContext,
8080
) -> None:
8181
# Given
8282
flagsmith._evaluation_context = evaluation_context
@@ -150,7 +150,7 @@ def test_get_identity_flags_calls_api_when_no_local_environment_with_traits(
150150
@responses.activate()
151151
def test_get_identity_flags_uses_local_environment_when_available(
152152
flagsmith: Flagsmith,
153-
evaluation_context: EvaluationContext,
153+
evaluation_context: SDKEvaluationContext,
154154
mocker: MockerFixture,
155155
) -> None:
156156
# Given
@@ -545,10 +545,10 @@ def test_initialise_flagsmith_with_proxies() -> None:
545545
assert flagsmith.session.proxies == proxies
546546

547547

548-
def test_offline_mode(evaluation_context: EvaluationContext) -> None:
548+
def test_offline_mode(evaluation_context: SDKEvaluationContext) -> None:
549549
# Given
550550
class DummyOfflineHandler:
551-
def get_evaluation_context(self) -> EvaluationContext:
551+
def get_evaluation_context(self) -> SDKEvaluationContext:
552552
return evaluation_context
553553

554554
# When
@@ -566,7 +566,7 @@ def get_evaluation_context(self) -> EvaluationContext:
566566
@responses.activate()
567567
def test_flagsmith_uses_offline_handler_if_set_and_no_api_response(
568568
mocker: MockerFixture,
569-
evaluation_context: EvaluationContext,
569+
evaluation_context: SDKEvaluationContext,
570570
) -> None:
571571
# Given
572572
api_url = "http://some.flagsmith.com/api/v1/"
@@ -599,7 +599,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response(
599599
@responses.activate()
600600
def test_offline_mode__local_evaluation__correct_fallback(
601601
mocker: MockerFixture,
602-
evaluation_context: EvaluationContext,
602+
evaluation_context: SDKEvaluationContext,
603603
caplog: pytest.LogCaptureFixture,
604604
) -> None:
605605
# Given

tests/test_models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import typing
22

33
import pytest
4-
from flag_engine.result.types import EvaluationResult, FlagResult
4+
from flag_engine.result.types import FlagResult
55

66
from flagsmith.models import Flag, Flags
7+
from flagsmith.types import SDKEvaluationResult
78

89

910
def test_flag_from_evaluation_result() -> None:
@@ -92,7 +93,7 @@ def test_flags_from_evaluation_result(
9293
expected_names: typing.List[str],
9394
) -> None:
9495
# Given
95-
evaluation_result: EvaluationResult = {
96+
evaluation_result: SDKEvaluationResult = {
9697
"flags": flags_result,
9798
"segments": [],
9899
}

0 commit comments

Comments
 (0)