Skip to content

Commit 3ade9e8

Browse files
Merge pull request #160 from Flagsmith/release/3.3.2
Release 3.3.2
2 parents 930101d + b6f26df commit 3ade9e8

File tree

6 files changed

+92
-21
lines changed

6 files changed

+92
-21
lines changed

flag_engine/api/fields.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,7 @@ def get_value(self, obj, attr, **kwargs):
4242
continue
4343

4444
existing_feature_state = features_map.get(fs.feature_id)
45-
if (
46-
not existing_feature_state
47-
or fs.version > existing_feature_state.version
48-
):
45+
if not existing_feature_state or fs > existing_feature_state:
4946
features_map[fs.feature_id] = fs
5047

5148
return list(features_map.values())

flag_engine/api/schemas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def serialize_feature_states(self, instance: typing.Any) -> typing.List[dict]:
9696

9797
existing_feature_state = feature_states.get(feature_state.feature_id)
9898
if not existing_feature_state or (
99-
feature_state.version > existing_feature_state.version
99+
feature_state > existing_feature_state
100100
):
101101
feature_states[feature_state.feature_id] = feature_state
102102
return self.feature_state_schema.dump(list(feature_states.values()), many=True)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="flagsmith-flag-engine",
5-
version="3.3.1",
5+
version="3.3.2",
66
author="Flagsmith",
77
author_email="[email protected]",
88
packages=find_packages(include=["flag_engine", "flag_engine.*"]),

tests/mock_django_classes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import typing
22
from dataclasses import dataclass, field
33
from datetime import datetime
4+
from unittest.mock import MagicMock
45

56
from flag_engine.utils.datetime import utcnow_with_tz
67

@@ -162,6 +163,7 @@ def __init__(
162163
] = None,
163164
version: int = 1,
164165
live_from: datetime = None,
166+
gt_mock: MagicMock = MagicMock(),
165167
):
166168
self.id = id
167169
self.feature_segment_id = getattr(feature_segment, "id", None)
@@ -178,6 +180,17 @@ def __init__(
178180
)
179181
self.version = version
180182
self.live_from = live_from or utcnow_with_tz()
183+
self.gt_mock = gt_mock
184+
185+
def __gt__(self, other: "DjangoFeatureState"):
186+
"""
187+
This is a workaround for now to avoid reimplementing the __gt__ method from the
188+
FeatureState class in the django application.
189+
# TODO: remove this logic from the engine and move it to the django repo.
190+
"""
191+
if self.gt_mock is None:
192+
raise NotImplementedError()
193+
return self.gt_mock(self, other)
181194

182195
def get_feature_state_value(self):
183196
return self.value

tests/unit/api/test_document_builders.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_build_environment_api_key_document(django_environment_api_key):
9696

9797

9898
def test_build_environment_document_with_multiple_feature_state_versions(
99-
django_project,
99+
django_project, mocker
100100
):
101101
# Given
102102
yesterday = utcnow_with_tz() - timedelta(days=1)
@@ -108,12 +108,31 @@ def test_build_environment_document_with_multiple_feature_state_versions(
108108
)
109109

110110
# and 3 feature states for the same feature, 2 with live_from dates in the past and
111-
# one with a live from date in the future
111+
# one with a live from date in the future. Note that, due to the fact that we are
112+
# using the FeatureState.__gt__ method to compare feature states (which is too
113+
# complicated to replicate here), we're exposing a mock to pass to the __gt__ method
114+
# on the DjangoFeatureState class we're using here. We then mimic the behaviour of
115+
# the method in the FeatureState class in the Django project.
112116
default_fs_kwargs = {"feature": feature, "live_from": yesterday, "enabled": True}
113-
feature_state_v1 = DjangoFeatureState(id=1, version=1, **default_fs_kwargs)
114-
feature_state_v2 = DjangoFeatureState(id=2, version=2, **default_fs_kwargs)
117+
118+
gt_mock_fs_1 = mocker.MagicMock(return_value=False)
119+
feature_state_v1 = DjangoFeatureState(
120+
id=1, version=1, **default_fs_kwargs, gt_mock=gt_mock_fs_1
121+
)
122+
123+
gt_mock_fs_2 = mocker.MagicMock(return_value=True)
124+
feature_state_v2 = DjangoFeatureState(
125+
id=2, version=2, **default_fs_kwargs, gt_mock=gt_mock_fs_2
126+
)
127+
128+
gt_mock_fs_3 = mocker.MagicMock(return_value=False)
115129
feature_state_v3 = DjangoFeatureState(
116-
id=3, version=3, feature=feature, enabled=True, live_from=tomorrow
130+
id=3,
131+
version=3,
132+
feature=feature,
133+
enabled=True,
134+
live_from=tomorrow,
135+
gt_mock=gt_mock_fs_3,
117136
)
118137

119138
django_environment = DjangoEnvironment(
@@ -132,21 +151,41 @@ def test_build_environment_document_with_multiple_feature_state_versions(
132151

133152

134153
def test_build_identity_document_with_multiple_feature_state_versions(
135-
django_environment, django_disabled_feature_state
154+
django_environment, django_disabled_feature_state, mocker
136155
):
137156
# Given
138157
yesterday = utcnow_with_tz() - timedelta(days=1)
139158
tomorrow = utcnow_with_tz() + timedelta(days=1)
140159
feature = django_disabled_feature_state.feature
141160

161+
gt_mock_identity_fs_1 = mocker.MagicMock(return_value=False)
142162
identity_feature_state_v1 = DjangoFeatureState(
143-
id=2, version=1, feature=feature, live_from=yesterday, enabled=True
163+
id=2,
164+
version=1,
165+
feature=feature,
166+
live_from=yesterday,
167+
enabled=True,
168+
gt_mock=gt_mock_identity_fs_1,
144169
)
170+
171+
gt_mock_identity_fs_2 = mocker.MagicMock(return_value=True)
145172
identity_feature_state_v2 = DjangoFeatureState(
146-
id=3, version=2, feature=feature, live_from=yesterday, enabled=True
173+
id=3,
174+
version=2,
175+
feature=feature,
176+
live_from=yesterday,
177+
enabled=True,
178+
gt_mock=gt_mock_identity_fs_2,
147179
)
180+
181+
gt_mock_identity_fs_3 = mocker.MagicMock(return_value=False)
148182
identity_feature_state_v3 = DjangoFeatureState(
149-
id=3, version=2, feature=feature, live_from=tomorrow, enabled=True
183+
id=3,
184+
version=2,
185+
feature=feature,
186+
live_from=tomorrow,
187+
enabled=True,
188+
gt_mock=gt_mock_identity_fs_3,
150189
)
151190
django_identity = DjangoIdentity(
152191
id=1,

tests/unit/api/test_fields.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import pytest
55
from marshmallow import fields
66

7+
from flag_engine.features.constants import STANDARD
78
from flag_engine.utils.datetime import utcnow_with_tz
89
from flag_engine.api.fields import (
910
APITraitValueField,
1011
DjangoFeatureStatesRelatedManagerField,
1112
DjangoRelatedManagerField,
1213
)
14+
from tests.mock_django_classes import DjangoFeatureState, DjangoFeature
1315

1416

1517
@pytest.mark.parametrize(
@@ -82,7 +84,9 @@ def filter_func(e):
8284
assert serialized_data == [3, 4]
8385

8486

85-
def test_django_feature_state_related_manager_field_serialize_discards_old_versions():
87+
def test_django_feature_state_related_manager_field_serialize_discards_old_versions(
88+
django_project,
89+
):
8690
# Given
8791
# a mock object to serialize
8892
attribute_name = "feature_states"
@@ -91,12 +95,30 @@ def test_django_feature_state_related_manager_field_serialize_discards_old_versi
9195
# and which has some 'feature states' associated with it in the way that you'd
9296
# expect a django object to. Each feature state associated with the same feature
9397
# but with incrementing version numbers.
94-
feature_id = 1
9598
yesterday = utcnow_with_tz() - timedelta(days=1)
96-
feature_states = [
97-
mock.MagicMock(id=i, feature_id=feature_id, version=i, live_from=yesterday)
98-
for i in (1, 2, 3, 4, 5)
99-
]
99+
100+
def gt_mock_side_effect(first, second):
101+
# Simplified version of FeatureState.__gt__ from the django project.
102+
return first.live_from < utcnow_with_tz() and (
103+
first.live_from > second.live_from or first.version > second.version
104+
)
105+
106+
django_feature = DjangoFeature(
107+
id=1, name="test_feature", project=django_project, type=STANDARD
108+
)
109+
110+
feature_states = []
111+
for i in range(1, 6):
112+
mock_fs = DjangoFeatureState(
113+
id=i,
114+
feature=django_feature,
115+
version=i,
116+
live_from=yesterday,
117+
enabled=True,
118+
gt_mock=mock.MagicMock(side_effect=gt_mock_side_effect),
119+
)
120+
feature_states.append(mock_fs)
121+
100122
getattr(object_to_serialize, attribute_name).all.return_value = feature_states
101123

102124
# and a filter function which will filter out the last feature state based on it's

0 commit comments

Comments
 (0)