Skip to content

Commit 5b607e8

Browse files
committed
fix coverage
1 parent 4364c6e commit 5b607e8

File tree

5 files changed

+107
-198
lines changed

5 files changed

+107
-198
lines changed

flag_engine/features/models.py

Lines changed: 7 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import math
21
import typing
32
import uuid
3+
import warnings
44

5-
from annotated_types import Ge, Le, SupportsLt
5+
from annotated_types import Ge, Le
66
from pydantic import UUID4, BaseModel, Field, model_validator
77
from pydantic_collections import BaseCollectionModel
88
from typing_extensions import Annotated
99

1010
from flag_engine.utils.exceptions import InvalidPercentageAllocation
11-
from flag_engine.utils.hashing import get_hashed_percentage_for_object_ids
1211

1312

1413
class FeatureModel(BaseModel):
@@ -19,9 +18,6 @@ class FeatureModel(BaseModel):
1918
def __eq__(self, other: object) -> bool:
2019
return isinstance(other, FeatureModel) and self.id == other.id
2120

22-
def __hash__(self) -> int:
23-
return hash(self.id)
24-
2521

2622
class MultivariateFeatureOptionModel(BaseModel):
2723
value: typing.Any
@@ -88,72 +84,15 @@ def set_value(self, value: typing.Any) -> None:
8884

8985
def get_value(self, identity_id: typing.Union[None, int, str] = None) -> typing.Any:
9086
"""
91-
Get the value of the feature state.
87+
DEPRECATED: Get the value of the feature state.
88+
Use the `feature_state_value` field directly instead.
9289
9390
:param identity_id: a unique identifier for the identity, can be either a
9491
numeric id or a string but must be unique for the identity.
9592
:return: the value of the feature state.
9693
"""
97-
if identity_id and len(self.multivariate_feature_state_values) > 0:
98-
return self._get_multivariate_value(identity_id)
99-
return self.feature_state_value
100-
101-
def is_higher_segment_priority(self, other: "FeatureStateModel") -> bool:
102-
"""
103-
Returns `True` if `self` is higher segment priority than `other`
104-
(i.e. has lower value for feature_segment.priority)
105-
106-
NOTE:
107-
A segment will be considered higher priority only if:
108-
1. `other` does not have a feature segment(i.e: it is an environment feature state or it's a
109-
feature state with feature segment but from an old document that does not have `feature_segment.priority`)
110-
but `self` does.
111-
112-
2. `other` have a feature segment with high priority
113-
114-
"""
115-
116-
if other_feature_segment := other.feature_segment:
117-
if (
118-
other_feature_segment_priority := other_feature_segment.priority
119-
) is not None:
120-
return (
121-
getattr(
122-
self.feature_segment,
123-
"priority",
124-
math.inf,
125-
)
126-
< other_feature_segment_priority
127-
)
128-
return False
129-
130-
def _get_multivariate_value(
131-
self, identity_id: typing.Union[int, str]
132-
) -> typing.Any:
133-
percentage_value = get_hashed_percentage_for_object_ids(
134-
[self.django_id or self.featurestate_uuid, identity_id]
94+
warnings.warn(
95+
"get_value is deprecated, use feature_state_value directly.",
96+
DeprecationWarning,
13597
)
136-
137-
# Iterate over the mv options in order of id (so we get the same value each
138-
# time) to determine the correct value to return to the identity based on
139-
# the percentage allocations of the multivariate options. This gives us a
140-
# way to ensure that the same value is returned every time we use the same
141-
# percentage value.
142-
start_percentage = 0.0
143-
144-
def _mv_fs_sort_key(mv_value: MultivariateFeatureStateValueModel) -> SupportsLt:
145-
return mv_value.id or mv_value.mv_fs_value_uuid
146-
147-
for mv_value in sorted(
148-
self.multivariate_feature_state_values,
149-
key=_mv_fs_sort_key,
150-
):
151-
limit = mv_value.percentage_allocation + start_percentage
152-
if start_percentage <= percentage_value < limit:
153-
return mv_value.multivariate_feature_option.value
154-
155-
start_percentage = limit
156-
157-
# default to return the control value if no MV values found, although this
158-
# should never happen
15998
return self.feature_state_value

flag_engine/segments/evaluator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def _evaluate_modulo(
296296
if not isinstance(context_value, (int, float)):
297297
return False
298298

299-
if segment_value is None:
299+
if not segment_value:
300300
return False
301301

302302
try:

tests/unit/conftest.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from flag_engine.features.models import (
1010
FeatureModel,
1111
FeatureStateModel,
12-
MultivariateFeatureOptionModel,
13-
MultivariateFeatureStateValueModel,
1412
)
1513
from flag_engine.identities.models import IdentityModel
1614
from flag_engine.identities.traits.models import TraitModel
@@ -185,17 +183,6 @@ def segment_override_fs(
185183
return fs
186184

187185

188-
@pytest.fixture()
189-
def mv_feature_state_value() -> MultivariateFeatureStateValueModel:
190-
return MultivariateFeatureStateValueModel(
191-
id=1,
192-
multivariate_feature_option=MultivariateFeatureOptionModel(
193-
id=1, value="test_value"
194-
),
195-
percentage_allocation=100,
196-
)
197-
198-
199186
@pytest.fixture()
200187
def environment_with_segment_override(
201188
environment: EnvironmentModel,

tests/unit/features/test_features_models.py

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
from unittest import mock
2-
31
import pytest
42
from pydantic import ValidationError
5-
from pytest_mock import MockerFixture
63

7-
from flag_engine.features.constants import STANDARD
84
from flag_engine.features.models import (
95
FeatureModel,
106
FeatureStateModel,
@@ -135,108 +131,3 @@ def test_feature_state_get_value_no_mv_values(feature_1: FeatureModel) -> None:
135131
# Then
136132
# the default value is always returned, even if an identity id is provided
137133
assert feature_state.get_value() == feature_state.get_value(1) == value
138-
139-
140-
mv_feature_control_value = "control"
141-
mv_feature_value_1 = "foo"
142-
mv_feature_value_2 = "bar"
143-
144-
145-
@pytest.mark.parametrize(
146-
"percentage_value, expected_value",
147-
(
148-
(10, mv_feature_value_1),
149-
(40, mv_feature_value_2),
150-
(70, mv_feature_control_value),
151-
),
152-
)
153-
@mock.patch("flag_engine.features.models.get_hashed_percentage_for_object_ids")
154-
def test_feature_state_get_value_mv_values(
155-
mock_get_hashed_percentage: mock.Mock,
156-
percentage_value: int,
157-
expected_value: str,
158-
) -> None:
159-
# Given
160-
# a feature
161-
my_feature = FeatureModel(id=1, name="mv_feature", type=STANDARD)
162-
163-
# with some multivariate feature options
164-
mv_feature_option_1 = MultivariateFeatureOptionModel(id=1, value=mv_feature_value_1)
165-
mv_feature_option_2 = MultivariateFeatureOptionModel(id=2, value=mv_feature_value_2)
166-
167-
# and associated values
168-
mv_feature_state_value_1 = MultivariateFeatureStateValueModel(
169-
id=1, multivariate_feature_option=mv_feature_option_1, percentage_allocation=30
170-
)
171-
mv_feature_state_value_2 = MultivariateFeatureStateValueModel(
172-
id=2, multivariate_feature_option=mv_feature_option_2, percentage_allocation=30
173-
)
174-
175-
# and we assign the above to a feature state
176-
mv_feature_state = FeatureStateModel(
177-
django_id=1,
178-
feature=my_feature,
179-
enabled=True,
180-
multivariate_feature_state_values=[
181-
mv_feature_state_value_1,
182-
mv_feature_state_value_2,
183-
],
184-
)
185-
mv_feature_state.set_value(mv_feature_control_value)
186-
187-
# and we mock the function which gets the percentage value for an identity to
188-
# return a deterministic value so we know which value to expect
189-
mock_get_hashed_percentage.return_value = percentage_value
190-
191-
# Then
192-
# the value of the feature state is correct based on the percentage value returned
193-
assert mv_feature_state.get_value(identity_id=1) == expected_value
194-
195-
196-
def test_get_value_uses_django_id_for_multivariate_value_calculation_if_not_none(
197-
feature_1: FeatureModel,
198-
mv_feature_state_value: MultivariateFeatureStateValueModel,
199-
mocker: MockerFixture,
200-
) -> None:
201-
# Given
202-
mocked_get_hashed_percentage = mocker.patch(
203-
"flag_engine.features.models.get_hashed_percentage_for_object_ids",
204-
return_value=10,
205-
)
206-
identity_id = 1
207-
feature_state = FeatureStateModel(
208-
django_id=1,
209-
feature=feature_1,
210-
enabled=True,
211-
multivariate_feature_state_values=[mv_feature_state_value],
212-
)
213-
# When
214-
feature_state.get_value(identity_id=identity_id)
215-
# Then
216-
mocked_get_hashed_percentage.assert_called_with(
217-
[feature_state.django_id, identity_id]
218-
)
219-
220-
221-
def test_get_value_uses_featuestate_uuid_for_multivariate_value_calculation_if_django_id_is_not_present(
222-
feature_1: FeatureModel,
223-
mv_feature_state_value: MultivariateFeatureStateValueModel,
224-
mocker: MockerFixture,
225-
) -> None:
226-
# Given
227-
mocked_get_hashed_percentage = mocker.patch(
228-
"flag_engine.features.models.get_hashed_percentage_for_object_ids",
229-
return_value=10,
230-
)
231-
identity_id = 1
232-
feature_state = FeatureStateModel(
233-
feature=feature_1,
234-
enabled=True,
235-
multivariate_feature_state_values=[mv_feature_state_value],
236-
)
237-
# When
238-
feature_state.get_value(identity_id=identity_id)
239-
# Then
240-
mocked_get_hashed_percentage.assert_called_with(
241-
[feature_state.featurestate_uuid, identity_id]
242-
)

0 commit comments

Comments
 (0)