Skip to content

Commit 59f7994

Browse files
author
Matthew Elwell
authored
Merge pull request #66 from Flagsmith/release/1.4.3
Release 1.4.3
2 parents f6ac1bb + 95a5d48 commit 59f7994

File tree

16 files changed

+173
-40
lines changed

16 files changed

+173
-40
lines changed

flag_engine/django_transform/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class DjangoFeatureStateSchema(BaseFeatureStateSchema):
2525
fields.Nested(MultivariateFeatureStateValueSchema), dump_only=True
2626
)
2727

28+
django_id = fields.Int(attribute="id")
29+
2830
def serialize_feature_state_value(self, instance) -> typing.Any:
2931
return instance.get_feature_state_value()
3032

flag_engine/features/models.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
import uuid
23
from dataclasses import dataclass, field
34

45
from flag_engine.utils.hashing import get_hashed_percentage_for_object_ids
@@ -31,9 +32,10 @@ class MultivariateFeatureStateValueModel:
3132

3233
@dataclass
3334
class FeatureStateModel:
34-
id: int
3535
feature: FeatureModel
3636
enabled: bool
37+
django_id: int = None
38+
featurestate_uuid: str = field(default_factory=uuid.uuid4)
3739
_value: typing.Any = field(default=None, init=False)
3840
multivariate_feature_state_values: typing.List[
3941
MultivariateFeatureStateValueModel
@@ -52,7 +54,9 @@ def get_feature_state_value(self):
5254
return self.get_value()
5355

5456
def _get_multivariate_value(self, identity_id: int) -> typing.Any:
55-
percentage_value = get_hashed_percentage_for_object_ids([self.id, identity_id])
57+
percentage_value = get_hashed_percentage_for_object_ids(
58+
[self.django_id or self.featurestate_uuid, identity_id]
59+
)
5660

5761
# Iterate over the mv options in order of id (so we get the same value each
5862
# time) to determine the correct value to return to the identity based on

flag_engine/features/schemas.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uuid
2+
13
from marshmallow import EXCLUDE, Schema, fields, post_load, validate
24

35
from flag_engine.features.models import (
@@ -35,10 +37,13 @@ class Meta:
3537

3638

3739
class BaseFeatureStateSchema(Schema):
38-
id = fields.Int()
40+
featurestate_uuid = fields.Str(dump_default=uuid.uuid4)
3941
feature = fields.Nested(FeatureSchema)
4042
enabled = fields.Bool()
4143

44+
class Meta:
45+
unknown = EXCLUDE
46+
4247

4348
class FeatureStateSchema(BaseFeatureStateSchema):
4449
feature_state_value = fields.Field(allow_none=True, required=False)

flag_engine/identities/builders.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
from marshmallow import Schema
2+
13
from flag_engine.identities.models import IdentityModel
24
from flag_engine.identities.schemas import IdentitySchema
35

46
identity_schema = IdentitySchema()
57

68

7-
def build_identity_dict(identity_model: IdentityModel) -> dict:
8-
return identity_schema.dump(identity_model)
9+
def build_identity_dict(
10+
identity_model: IdentityModel, schema: Schema = identity_schema
11+
) -> dict:
12+
return schema.dump(identity_model)
913

1014

1115
def build_identity_model(identity_dict: dict) -> IdentityModel:

flag_engine/identities/models.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
import uuid
44
from dataclasses import dataclass, field
55

6-
from flag_engine.features.models import FeatureStateModel
76
from flag_engine.identities.traits.models import TraitModel
7+
from flag_engine.utils.collections import IdentityFeaturesList
88

99

1010
@dataclass
1111
class IdentityModel:
1212
identifier: str
1313
environment_api_key: str
1414
created_date: datetime = field(default_factory=datetime.datetime.now)
15-
identity_features: typing.List[FeatureStateModel] = field(default_factory=list)
15+
identity_features: IdentityFeaturesList = field(
16+
default_factory=IdentityFeaturesList
17+
)
1618
identity_traits: typing.List[TraitModel] = field(default_factory=list)
1719
identity_uuid: str = field(default_factory=uuid.uuid4)
1820
django_id: int = None

flag_engine/identities/schemas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class BaseIdentitySchema(Schema):
1414
identifier = fields.Str()
1515
created_date = fields.DateTime()
16-
identity_uuid = fields.UUID(default=uuid.uuid4)
16+
identity_uuid = fields.UUID(dump_default=uuid.uuid4)
1717
environment_api_key = fields.Str()
1818

1919
@post_dump

flag_engine/utils/collections.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from collections import UserList
2+
3+
from flag_engine.features.models import FeatureStateModel
4+
from flag_engine.utils.exceptions import DuplicateFeatureState
5+
6+
7+
class IdentityFeaturesList(UserList):
8+
def append(self, feature_state: FeatureStateModel):
9+
if [fs for fs in self.data if fs.feature.id == feature_state.feature.id]:
10+
raise DuplicateFeatureState("feature state for this feature already exists")
11+
12+
super().append(feature_state)

flag_engine/utils/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
class FeatureStateNotFound(Exception):
22
pass
3+
4+
5+
class DuplicateFeatureState(Exception):
6+
pass

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="1.4.2",
5+
version="1.4.3",
66
author="Flagsmith",
77
author_email="[email protected]",
88
packages=find_packages(include=["flag_engine", "flag_engine.*"]),

tests/unit/conftest.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
from flag_engine.environments.models import EnvironmentModel
66
from flag_engine.features.constants import STANDARD
7-
from flag_engine.features.models import FeatureModel, FeatureStateModel
7+
from flag_engine.features.models import (
8+
FeatureModel,
9+
FeatureStateModel,
10+
MultivariateFeatureOptionModel,
11+
MultivariateFeatureStateValueModel,
12+
)
813
from flag_engine.identities.models import IdentityModel
914
from flag_engine.identities.traits.models import TraitModel
1015
from flag_engine.organisations.models import OrganisationModel
@@ -78,8 +83,8 @@ def environment(feature_1, feature_2, project):
7883
api_key="api-key",
7984
project=project,
8085
feature_states=[
81-
FeatureStateModel(id=1, feature=feature_1, enabled=True),
82-
FeatureStateModel(id=2, feature=feature_2, enabled=False),
86+
FeatureStateModel(django_id=1, feature=feature_1, enabled=True),
87+
FeatureStateModel(django_id=2, feature=feature_2, enabled=False),
8388
],
8489
)
8590

@@ -112,14 +117,23 @@ def identity_in_segment(trait_matching_segment, environment):
112117
@pytest.fixture()
113118
def segment_override_fs(segment, feature_1):
114119
fs = FeatureStateModel(
115-
id=4,
120+
django_id=4,
116121
feature=feature_1,
117122
enabled=False,
118123
)
119124
fs.set_value("segment_override")
120125
return fs
121126

122127

128+
@pytest.fixture()
129+
def mv_feature_state_value():
130+
return MultivariateFeatureStateValueModel(
131+
id=1,
132+
multivariate_feature_option=MultivariateFeatureOptionModel(value="test_value"),
133+
percentage_allocation=100,
134+
)
135+
136+
123137
@pytest.fixture()
124138
def environment_with_segment_override(environment, segment_override_fs, segment):
125139
segment.feature_states.append(segment_override_fs)

0 commit comments

Comments
 (0)