Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,23 +495,25 @@ def flush(self):
return
return self._event_processor.flush()

def variation(self, key: str, context: Context, default: Any) -> Any:
def variation(self, key: str, context: Context, default: Any, send_events: bool = True) -> Any:
"""Calculates the value of a feature flag for a given context.

:param key: the unique key for the feature flag
:param context: the evaluation context
:param default: the default value of the flag, to be used if the value is not
available from LaunchDarkly
:param send_events: whether to send evaluation events to LaunchDarkly. Defaults to True.
Set to False to evaluate the flag without generating analytics events.
:return: the variation for the given context, or the ``default`` value if the flag cannot be evaluated
"""

def evaluate():
detail, _ = self._evaluate_internal(key, context, default, self._event_factory_default)
detail, _ = self._evaluate_internal(key, context, default, self._event_factory_default, send_events)
return _EvaluationWithHookResult(evaluation_detail=detail)

return self.__evaluate_with_hooks(key=key, context=context, default_value=default, method="variation", block=evaluate).evaluation_detail.value

def variation_detail(self, key: str, context: Context, default: Any) -> EvaluationDetail:
def variation_detail(self, key: str, context: Context, default: Any, send_events: bool = True) -> EvaluationDetail:
"""Calculates the value of a feature flag for a given context, and returns an object that
describes the way the value was determined.

Expand All @@ -522,12 +524,14 @@ def variation_detail(self, key: str, context: Context, default: Any) -> Evaluati
:param context: the evaluation context
:param default: the default value of the flag, to be used if the value is not
available from LaunchDarkly
:param send_events: whether to send evaluation events to LaunchDarkly. Defaults to True.
Set to False to evaluate the flag without generating analytics events.
:return: an :class:`ldclient.evaluation.EvaluationDetail` object that includes the feature
flag value and evaluation reason
"""

def evaluate():
detail, _ = self._evaluate_internal(key, context, default, self._event_factory_with_reasons)
detail, _ = self._evaluate_internal(key, context, default, self._event_factory_with_reasons, send_events)
return _EvaluationWithHookResult(evaluation_detail=detail)

return self.__evaluate_with_hooks(key=key, context=context, default_value=default, method="variation_detail", block=evaluate).evaluation_detail
Expand Down Expand Up @@ -562,7 +566,7 @@ def evaluate():
hook_result = self.__evaluate_with_hooks(key=key, context=context, default_value=default_stage.value, method="migration_variation", block=evaluate)
return hook_result.results['default_stage'], hook_result.results['tracker']

def _evaluate_internal(self, key: str, context: Context, default: Any, event_factory) -> Tuple[EvaluationDetail, Optional[FeatureFlag]]:
def _evaluate_internal(self, key: str, context: Context, default: Any, event_factory, send_events: bool = True) -> Tuple[EvaluationDetail, Optional[FeatureFlag]]:
default = self._config.get_default(key, default)

if self._config.offline:
Expand All @@ -574,7 +578,8 @@ def _evaluate_internal(self, key: str, context: Context, default: Any, event_fac
else:
log.warning("Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: " + str(default) + " for feature key: " + key)
reason = error_reason('CLIENT_NOT_READY')
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
if send_events:
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
return EvaluationDetail(default, None, reason), None

if not context.valid:
Expand All @@ -587,27 +592,32 @@ def _evaluate_internal(self, key: str, context: Context, default: Any, event_fac
log.error("Unexpected error while retrieving feature flag \"%s\": %s" % (key, repr(e)))
log.debug(traceback.format_exc())
reason = error_reason('EXCEPTION')
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
if send_events:
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
return EvaluationDetail(default, None, reason), None
if not flag:
reason = error_reason('FLAG_NOT_FOUND')
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
if send_events:
self._send_event(event_factory.new_unknown_flag_event(key, context, default, reason))
return EvaluationDetail(default, None, reason), None
else:
try:
result = self._evaluator.evaluate(flag, context, event_factory)
for event in result.events or []:
self._send_event(event)
if send_events:
for event in result.events or []:
self._send_event(event)
detail = result.detail
if detail.is_default_value():
detail = EvaluationDetail(default, None, detail.reason)
self._send_event(event_factory.new_eval_event(flag, context, detail, default))
if send_events:
self._send_event(event_factory.new_eval_event(flag, context, detail, default))
return detail, flag
except Exception as e:
log.error("Unexpected error while evaluating feature flag \"%s\": %s" % (key, repr(e)))
log.debug(traceback.format_exc())
reason = error_reason('EXCEPTION')
self._send_event(event_factory.new_default_event(flag, context, default, reason))
if send_events:
self._send_event(event_factory.new_default_event(flag, context, default, reason))
return EvaluationDetail(default, None, reason), flag

def all_flags_state(self, context: Context, **kwargs) -> FeatureFlagsState:
Expand Down
9 changes: 9 additions & 0 deletions ldclient/testing/test_ldclient_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,12 @@ def test_no_event_for_existing_feature_with_invalid_context():
bad_context = Context.create('')
assert 'default' == client.variation('feature.key', bad_context, default='default')
assert count_events(client) == 0


def test_no_event_when_send_events_false():
feature = build_off_flag_with_value('feature.key', 'value').track_events(True).build()
store = InMemoryFeatureStore()
store.init({FEATURES: {feature.key: feature.to_json_dict()}})
with make_client(store) as client:
assert 'value' == client.variation(feature.key, context, default='default', send_events=False)
assert count_events(client) == 0
Loading