From 3ad2dddf7adbf044a779abe9bd68162ec3d36bbb Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 26 Apr 2022 17:34:29 +0100 Subject: [PATCH 1/5] Version bump 3.0.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 947be11..5170857 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="flagsmith-flag-engine", - version="2.0.2", + version="3.0.0", author="Flagsmith", author_email="support@flagsmith.com", packages=find_packages(include=["flag_engine", "flag_engine.*"]), From 6bed1c7e86ed5cf8112bbd5d3e01bc5e79f6eb6e Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 10 Nov 2022 16:17:43 +0530 Subject: [PATCH 2/5] feat(identity/update_traits): update return type (#146) * feat(identity/update_traits): update return type Return boolean(and all the traits) indicating wether traits changed or not --- flag_engine/identities/models.py | 18 ++++++++--- .../unit/identities/test_identities_models.py | 32 +++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/flag_engine/identities/models.py b/flag_engine/identities/models.py index af64361..eafdb0c 100644 --- a/flag_engine/identities/models.py +++ b/flag_engine/identities/models.py @@ -28,17 +28,25 @@ def composite_key(self) -> str: def generate_composite_key(env_key: str, identifier: str) -> str: return f"{env_key}_{identifier}" - def update_traits(self, traits: typing.List[TraitModel]) -> typing.List[TraitModel]: + def update_traits( + self, traits: typing.List[TraitModel] + ) -> typing.Tuple[typing.List[TraitModel], bool]: existing_traits = {trait.trait_key: trait for trait in self.identity_traits} + traits_changed = False for trait in traits: - if trait.trait_value is None: - existing_traits.pop(trait.trait_key, None) - else: + existing_trait = existing_traits.get(trait.trait_key) + + if trait.trait_value is None and existing_trait: + existing_traits.pop(trait.trait_key) + traits_changed = True + + elif getattr(existing_trait, "trait_value", None) != trait.trait_value: existing_traits[trait.trait_key] = trait + traits_changed = True self.identity_traits = list(existing_traits.values()) - return self.identity_traits + return self.identity_traits, traits_changed def prune_features(self, valid_feature_names: typing.List[str]) -> None: self.identity_features = IdentityFeaturesList( diff --git a/tests/unit/identities/test_identities_models.py b/tests/unit/identities/test_identities_models.py index e5fc02a..eeeb5c8 100644 --- a/tests/unit/identities/test_identities_models.py +++ b/tests/unit/identities/test_identities_models.py @@ -44,10 +44,13 @@ def test_update_traits_remove_traits_with_none_value(identity_in_segment): trait_to_remove = TraitModel(trait_key=trait_key, trait_value=None) # When - updated_traits = identity_in_segment.update_traits([trait_to_remove]) + updated_traits, traits_updated = identity_in_segment.update_traits( + [trait_to_remove] + ) # Then assert identity_in_segment.identity_traits == updated_traits == [] + assert traits_updated is True def test_update_identity_traits_updates_trait_value(identity_in_segment): @@ -57,12 +60,15 @@ def test_update_identity_traits_updates_trait_value(identity_in_segment): trait_to_update = TraitModel(trait_key=trait_key, trait_value=trait_value) # When - updated_traits = identity_in_segment.update_traits([trait_to_update]) + updated_traits, traits_updated = identity_in_segment.update_traits( + [trait_to_update] + ) # Then assert updated_traits == identity_in_segment.identity_traits assert len(identity_in_segment.identity_traits) == 1 assert identity_in_segment.identity_traits[0] == trait_to_update + assert traits_updated is True def test_update_traits_adds_new_traits(identity_in_segment): @@ -70,12 +76,32 @@ def test_update_traits_adds_new_traits(identity_in_segment): new_trait = TraitModel(trait_key="new_key", trait_value="foobar") # When - updated_traits = identity_in_segment.update_traits([new_trait]) + updated_traits, traits_updated = identity_in_segment.update_traits([new_trait]) # Then assert updated_traits == identity_in_segment.identity_traits assert len(identity_in_segment.identity_traits) == 2 assert new_trait in identity_in_segment.identity_traits + assert traits_updated is True + + +def test_update_traits_returns_false_if_traits_are_not_updated(identity_in_segment): + # Given + trait_key = identity_in_segment.identity_traits[0].trait_key + trait_value = identity_in_segment.identity_traits[0].trait_value + + trait_to_update = TraitModel(trait_key=trait_key, trait_value=trait_value) + + # When + updated_traits, traits_updated = identity_in_segment.update_traits( + [trait_to_update] + ) + + # Then + assert updated_traits == identity_in_segment.identity_traits + assert len(identity_in_segment.identity_traits) == 1 + assert identity_in_segment.identity_traits[0] == trait_to_update + assert traits_updated is False def test_appending_feature_states_raises_duplicate_feature_state_if_fs_for_the_feature_already_exists( From 1c61222f9fb309a2c6d87e857a6481762dd61b89 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 10 Nov 2022 16:33:20 +0530 Subject: [PATCH 3/5] fix setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index cf6ffdf..5170857 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ setup( name="flagsmith-flag-engine", version="3.0.0", - version="2.3.0", author="Flagsmith", author_email="support@flagsmith.com", packages=find_packages(include=["flag_engine", "flag_engine.*"]), From 8c110a14eeee46b4d6ead8602e92102bea1f0a10 Mon Sep 17 00:00:00 2001 From: Gagan Date: Fri, 11 Nov 2022 15:13:21 +0530 Subject: [PATCH 4/5] feat(project/models): Add enable_realtime_updates field (#147) --- flag_engine/projects/models.py | 1 + flag_engine/projects/schemas.py | 1 + 2 files changed, 2 insertions(+) diff --git a/flag_engine/projects/models.py b/flag_engine/projects/models.py index 17102d5..be8c735 100644 --- a/flag_engine/projects/models.py +++ b/flag_engine/projects/models.py @@ -12,3 +12,4 @@ class ProjectModel: organisation: OrganisationModel hide_disabled_flags: bool segments: typing.List[SegmentModel] = field(default_factory=list) + enable_realtime_updates: bool = False diff --git a/flag_engine/projects/schemas.py b/flag_engine/projects/schemas.py index 96929cc..5908bd1 100644 --- a/flag_engine/projects/schemas.py +++ b/flag_engine/projects/schemas.py @@ -11,6 +11,7 @@ class BaseProjectSchema(Schema): name = fields.Str() organisation = fields.Nested(OrganisationSchema) hide_disabled_flags = fields.Bool() + enable_realtime_updates = fields.Bool() class ProjectSchema(LoadToModelMixin, BaseProjectSchema): From a25f0a67d434eed0315f0f9be1d7501a26149a24 Mon Sep 17 00:00:00 2001 From: Gagan Date: Fri, 11 Nov 2022 16:12:40 +0530 Subject: [PATCH 5/5] feat(enviroment): add updated_at field (#148) * feat(enviroment): add updated_at field * use default_factory for updated_at --- flag_engine/environments/models.py | 1 + flag_engine/environments/schemas.py | 1 + 2 files changed, 2 insertions(+) diff --git a/flag_engine/environments/models.py b/flag_engine/environments/models.py index cbd3524..c95cde8 100644 --- a/flag_engine/environments/models.py +++ b/flag_engine/environments/models.py @@ -38,6 +38,7 @@ class EnvironmentModel: project: ProjectModel feature_states: typing.List[FeatureStateModel] = field(default_factory=list) allow_client_traits: bool = True + updated_at: datetime = field(default_factory=utcnow_with_tz) amplitude_config: IntegrationModel = None segment_config: IntegrationModel = None diff --git a/flag_engine/environments/schemas.py b/flag_engine/environments/schemas.py index 7a998a9..dc6104c 100644 --- a/flag_engine/environments/schemas.py +++ b/flag_engine/environments/schemas.py @@ -39,6 +39,7 @@ class BaseEnvironmentSchema(Schema): id = fields.Int() api_key = fields.Str() allow_client_traits = fields.Bool(required=False, default=True) + updated_at = fields.DateTime() segment_config = fields.Nested(IntegrationSchema, required=False, allow_none=True) heap_config = fields.Nested(IntegrationSchema, required=False, allow_none=True)