Skip to content

Commit 0687c30

Browse files
author
Matthew Elwell
authored
Add method for retrieving segments for an identity (#24)
1 parent 5b49607 commit 0687c30

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

flagsmith/flagsmith.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
from flag_engine.environments.builders import build_environment_model
88
from flag_engine.environments.models import EnvironmentModel
99
from flag_engine.identities.models import IdentityModel, TraitModel
10+
from flag_engine.segments.evaluator import get_identity_segments
1011
from requests.adapters import HTTPAdapter
1112
from urllib3 import Retry
1213

1314
from flagsmith.analytics import AnalyticsProcessor
1415
from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError
15-
from flagsmith.models import DefaultFlag, Flags
16+
from flagsmith.models import DefaultFlag, Flags, Segment
1617
from flagsmith.polling_manager import EnvironmentDataPollingManager
1718
from flagsmith.utils.identities import generate_identities_data
1819

@@ -43,7 +44,7 @@ def __init__(
4344
custom_headers: typing.Dict[str, typing.Any] = None,
4445
request_timeout_seconds: int = None,
4546
enable_local_evaluation: bool = False,
46-
environment_refresh_interval_seconds: int = 60,
47+
environment_refresh_interval_seconds: typing.Union[int, float] = 60,
4748
retries: Retry = None,
4849
enable_analytics: bool = False,
4950
default_flag_handler: typing.Callable[[str], DefaultFlag] = None,
@@ -129,6 +130,29 @@ def get_identity_flags(
129130
return self._get_identity_flags_from_document(identifier, traits)
130131
return self._get_identity_flags_from_api(identifier, traits)
131132

133+
def get_identity_segments(
134+
self, identifier: str, traits: typing.Dict[str, typing.Any] = None
135+
) -> typing.List[Segment]:
136+
"""
137+
Get a list of segments that the given identity is in.
138+
139+
:param identifier: a unique identifier for the identity in the current
140+
environment, e.g. email address, username, uuid
141+
:param traits: a dictionary of traits to add / update on the identity in
142+
Flagsmith, e.g. {"num_orders": 10}
143+
:return: list of Segment objects that the identity is part of.
144+
"""
145+
146+
if not self._environment:
147+
raise FlagsmithClientError(
148+
"Local evaluation required to obtain identity segments."
149+
)
150+
151+
traits = traits or {}
152+
identity_model = self._build_identity_model(identifier, **traits)
153+
segment_models = get_identity_segments(self._environment, identity_model)
154+
return [Segment(id=sm.id, name=sm.name) for sm in segment_models]
155+
132156
def update_environment(self):
133157
self._environment = self._get_environment_from_api()
134158

flagsmith/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,9 @@ def get_flag(self, feature_name: str) -> BaseFlag:
140140
self._analytics_processor.track_feature(flag.feature_id)
141141

142142
return flag
143+
144+
145+
@dataclass
146+
class Segment:
147+
id: int
148+
name: str

tests/conftest.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import string
55

66
import pytest
7+
import responses
78
from flag_engine.environments.builders import build_environment_model
89

910
from flagsmith import Flagsmith
1011
from flagsmith.analytics import AnalyticsProcessor
12+
from flagsmith.flagsmith import DEFAULT_API_URL
1113

1214
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
1315

@@ -24,6 +26,11 @@ def api_key():
2426
return "".join(random.sample(string.ascii_letters, 20))
2527

2628

29+
@pytest.fixture(scope="session")
30+
def server_api_key():
31+
return "ser.%s" % "".join(random.sample(string.ascii_letters, 20))
32+
33+
2734
@pytest.fixture()
2835
def flagsmith(api_key):
2936
return Flagsmith(environment_key=api_key)
@@ -35,6 +42,26 @@ def environment_json():
3542
yield f.read()
3643

3744

45+
@pytest.fixture()
46+
def local_eval_flagsmith(server_api_key, environment_json, mocker):
47+
mock_session = mocker.MagicMock()
48+
mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session)
49+
50+
mock_environment_document_response = mocker.MagicMock(status_code=200)
51+
mock_environment_document_response.json.return_value = json.loads(environment_json)
52+
mock_session.get.return_value = mock_environment_document_response
53+
54+
flagsmith = Flagsmith(
55+
environment_key=server_api_key,
56+
enable_local_evaluation=True,
57+
environment_refresh_interval_seconds=0.1,
58+
)
59+
60+
yield flagsmith
61+
62+
flagsmith.__del__()
63+
64+
3865
@pytest.fixture()
3966
def environment_model(environment_json):
4067
return build_environment_model(json.loads(environment_json))

tests/data/environment.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,30 @@
1111
},
1212
"id": 1,
1313
"hide_disabled_flags": false,
14-
"segments": []
14+
"segments": [
15+
{
16+
"id": 1,
17+
"name": "Test segment",
18+
"rules": [
19+
{
20+
"type": "ALL",
21+
"rules": [
22+
{
23+
"type": "ALL",
24+
"rules": [],
25+
"conditions": [
26+
{
27+
"operator": "EQUAL",
28+
"property_": "foo",
29+
"value": "bar"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
]
36+
}
37+
]
1538
},
1639
"segment_overrides": [],
1740
"id": 1,

tests/test_flagsmith.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,27 @@ def default_flag_handler(feature_name: str) -> DefaultFlag:
336336

337337
# Then
338338
assert flags.get_flag("some-feature") == default_flag
339+
340+
341+
def test_get_identity_segments_no_traits(local_eval_flagsmith, environment_model):
342+
# Given
343+
identifier = "identifier"
344+
345+
# When
346+
segments = local_eval_flagsmith.get_identity_segments(identifier)
347+
348+
# Then
349+
assert segments == []
350+
351+
352+
def test_get_identity_segments_with_valid_trait(local_eval_flagsmith, environment_model):
353+
# Given
354+
identifier = "identifier"
355+
traits = {"foo": "bar"} # obtained from data/environment.json
356+
357+
# When
358+
segments = local_eval_flagsmith.get_identity_segments(identifier, traits)
359+
360+
# Then
361+
assert len(segments) == 1
362+
assert segments[0].name == "Test segment" # obtained from data/environment.json

0 commit comments

Comments
 (0)