def generate_traits(self, trait_data_items, persist=False): """ Given a list of trait data items, validated by TraitSerializerFull, generate a list of TraitModel objects for the given identity. :param trait_data_items: list of dictionaries validated by TraitSerializerFull :param persist: determines whether the traits should be persisted to db :return: list of TraitModels """ trait_models = [] for trait_data_item in trait_data_items: trait_key = trait_data_item["trait_key"] trait_value = trait_data_item["trait_value"] trait_models.append( Trait( trait_key=trait_key, identity=self, **Trait.generate_trait_value_data(trait_value), ) ) if persist: Trait.objects.bulk_create(trait_models) return trait_models
def create_trait_for_identity( identity: Identity, trait_key: str, trait_value: typing.Any ): trait_value_data = Trait.generate_trait_value_data(trait_value) return Trait.objects.create( identity=identity, trait_key=trait_key, **trait_value_data )
def _make_temporary_trait(self, identity, trait_data): print(trait_data) return Trait(identity=identity, trait_key=trait_data.get('trait_key'), value_type=trait_data.get('value_type'), boolean_value=trait_data.get('boolean_value'), integer_value=trait_data.get('integer_value'), string_value=trait_data.get('string_value'), float_value=trait_data.get('float_value'))
def test_get_all_feature_states_returns_correct_value_when_traits_passed_manually( self, ): """ Verify that when traits are passed manually, then the segments are correctly analysed for the identity and the correct value is returned for the feature state. """ # Given - an identity with a trait that has an integer value of 10 trait_key = "trait-key" trait_value = 10 identity = Identity.objects.create(identifier="test-identity", environment=self.environment) trait = Trait( identity=identity, trait_key=trait_key, integer_value=trait_value, value_type=INTEGER, ) # and a segment that matches all identities with a trait value greater than or equal to 5 segment = Segment.objects.create(name="Test segment 1", project=self.project) rule = SegmentRule.objects.create(segment=segment, type=SegmentRule.ALL_RULE) Condition.objects.create(rule=rule, property=trait_key, value=5, operator=GREATER_THAN_INCLUSIVE) # and a feature flag default_state = False feature_flag = Feature.objects.create(project=self.project, name="test_flag", default_enabled=default_state) # which is overridden by the segment enabled_for_segment = not default_state feature_segment = FeatureSegment.objects.create( feature=feature_flag, segment=segment, environment=self.environment, priority=1, ) FeatureState.objects.create( feature=feature_flag, feature_segment=feature_segment, environment=self.environment, enabled=enabled_for_segment, ) # When - we get all feature states for an identity feature_states = identity.get_all_feature_states(traits=[trait]) # Then - the flag is returned with the correct state assert len(feature_states) == 1 assert feature_states[0].enabled == enabled_for_segment
def update_traits(self, trait_data_items): """ Given a list of traits, update any that already exist and create any new ones. Return the full list of traits for the given identity after these changes. :param trait_data_items: list of dictionaries validated by TraitSerializerFull :return: queryset of updated trait models """ current_traits = self.get_all_user_traits() new_traits = [] keys_to_delete = [] for trait_data_item in trait_data_items: trait_key = trait_data_item["trait_key"] trait_value = trait_data_item["trait_value"] if trait_value is None: # build a list of trait keys to delete having been nulled by the # input data keys_to_delete.append(trait_key) continue trait_value_data = Trait.generate_trait_value_data(trait_value) if current_traits.filter(trait_key=trait_key).exists(): current_trait = current_traits.get(trait_key=trait_key) for attr, value in trait_value_data.items(): setattr(current_trait, attr, value) current_trait.save() else: # create a new trait and append it to the list of new traits new_traits.append( Trait.objects.create( trait_key=trait_key, identity=self, **trait_value_data ) ) # delete the traits that had their keys set to None if keys_to_delete: current_traits.filter(trait_key__in=keys_to_delete).delete() # return the full list of traits for this identity by refreshing from the db # TODO: handle this in the above logic to avoid a second hit to the DB return self.get_all_user_traits()
def create(self, validated_data): identity = self._get_identity(validated_data["identity"]["identifier"]) trait_key = validated_data["trait_key"] trait_value = validated_data["trait_value"]["value"] trait_value_type = validated_data["trait_value"]["type"] value_key = Trait.get_trait_value_key_name(trait_value_type) defaults = { value_key: trait_value, "value_type": trait_value_type if trait_value_type in [FLOAT, INTEGER, BOOLEAN] else STRING, } return Trait.objects.update_or_create( identity=identity, trait_key=trait_key, defaults=defaults )[0]
def create(self, validated_data): identity = self._get_identity(validated_data['identity']['identifier']) trait_key = validated_data['trait_key'] trait_value = validated_data['trait_value']['value'] trait_value_type = validated_data['trait_value']['type'] value_key = Trait.get_trait_value_key_name(trait_value_type) defaults = { value_key: trait_value, 'value_type': trait_value_type if trait_value_type in [FLOAT, INTEGER, BOOLEAN] else STRING } return Trait.objects.update_or_create(identity=identity, trait_key=trait_key, defaults=defaults)[0]
def test_generate_trait_value_data_for_deserialized_data( deserialized_data, expected_data): assert Trait.generate_trait_value_data(deserialized_data) == expected_data
def test_generate_trait_value_data_for_value(value, expected_data): assert Trait.generate_trait_value_data(value) == expected_data