-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add all_flags, secure_mode_hash + more #51
Changes from 6 commits
4a197fa
b6ecb82
1093b0d
1f7ea6f
989878d
6ab8ed2
99bb605
a2a7032
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ Your first feature flag | |
1. Create a new feature flag on your [dashboard](https://app.launchdarkly.com) | ||
2. In your application code, use the feature's key to check wthether the flag is on for each user: | ||
|
||
if client.toggle("your.flag.key", {"key": "[email protected]"}, False): | ||
if client.variation("your.flag.key", {"key": "[email protected]"}, False): | ||
# application code to show the feature | ||
else: | ||
# the code to run if the feature is off | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
from __future__ import division, with_statement, absolute_import | ||
|
||
import hashlib | ||
import hmac | ||
import threading | ||
import time | ||
|
||
|
@@ -9,7 +11,7 @@ | |
from ldclient.event_consumer import EventConsumerImpl | ||
from ldclient.feature_requester import FeatureRequesterImpl | ||
from ldclient.feature_store import InMemoryFeatureStore | ||
from ldclient.flag import _get_off_variation, _evaluate_index, _get_variation, evaluate | ||
from ldclient.flag import evaluate | ||
from ldclient.interfaces import FeatureStore | ||
from ldclient.polling import PollingUpdateProcessor | ||
from ldclient.streaming import StreamingUpdateProcessor | ||
|
@@ -108,6 +110,7 @@ def __init__(self, api_key, config=None, start_wait=5): | |
""" :type: FeatureStore """ | ||
|
||
if self._config.offline: | ||
self._config.events_enabled = False | ||
log.info("Started LaunchDarkly Client in offline mode") | ||
return | ||
|
||
|
@@ -177,10 +180,14 @@ def _send_event(self, event): | |
|
||
def track(self, event_name, user, data=None): | ||
self._sanitize_user(user) | ||
if user.get('key', "") == "": | ||
log.warn("Missing or empty User key when calling track().") | ||
self._send_event({'kind': 'custom', 'key': event_name, 'user': user, 'data': data}) | ||
|
||
def identify(self, user): | ||
self._sanitize_user(user) | ||
if user.get('key', "") == "": | ||
log.warn("Missing or empty User key when calling identify().") | ||
self._send_event({'kind': 'identify', 'key': user.get('key'), 'user': user}) | ||
|
||
def is_offline(self): | ||
|
@@ -195,15 +202,19 @@ def flush(self): | |
return self._event_consumer.flush() | ||
|
||
def toggle(self, key, user, default): | ||
log.warn("Deprecated method: toggle() called. Use variation() instead.") | ||
return self.variation(key, user, default) | ||
|
||
def variation(self, key, user, default): | ||
default = self._config.get_default(key, default) | ||
self._sanitize_user(user) | ||
|
||
if self._config.offline: | ||
return default | ||
|
||
def send_event(value): | ||
def send_event(value, version=None): | ||
self._send_event({'kind': 'feature', 'key': key, | ||
'user': user, 'value': value, 'default': default}) | ||
'user': user, 'value': value, 'default': default, 'version': version}) | ||
|
||
if not self.is_initialized(): | ||
log.warn("Feature Flag evaluation attempted before client has finished initializing! Returning default: " | ||
|
@@ -222,26 +233,28 @@ def send_event(value): | |
send_event(default) | ||
return default | ||
|
||
if flag.get('on', False): | ||
value, prereq_events = evaluate(flag, user, self._store) | ||
if not self._config.offline: | ||
for e in prereq_events: | ||
self._send_event(e) | ||
value, events = evaluate(flag, user, self._store) | ||
for event in events or []: | ||
self._send_event(event) | ||
log.debug("Sending event: " + str(event)) | ||
|
||
if value is not None: | ||
send_event(value) | ||
return value | ||
if value is not None: | ||
send_event(value, flag.get('version')) | ||
return value | ||
|
||
if 'offVariation' in flag and flag['offVariation']: | ||
value = _get_variation(flag, flag['offVariation']) | ||
send_event(value) | ||
return value | ||
|
||
send_event(default) | ||
send_event(default, flag.get('version')) | ||
return default | ||
|
||
def all_flags(self, user): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jkodumal take note of these checks when implementing in other sdks. |
||
return {k: evaluate(v, user, self._store)[0] for k, v in self._store.all().items() or {}} | ||
|
||
def secure_mode_hash(self, user): | ||
if user.get('key', "") == "": | ||
return "" | ||
return hmac.new(self._api_key.encode(), user.get('key').encode(), hashlib.sha256).hexdigest() | ||
|
||
def _sanitize_user(self, user): | ||
@staticmethod | ||
def _sanitize_user(user): | ||
if 'key' in user: | ||
user['key'] = str(user['key']) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,29 +14,46 @@ | |
log = logging.getLogger(sys.modules[__name__].__name__) | ||
|
||
|
||
def evaluate(flag, user, store, prereq_events=[]): | ||
def evaluate(flag, user, store): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved all the flag eval logic into the flag file. |
||
prereq_events = [] | ||
if flag.get('on', False): | ||
value, prereq_events = _evaluate(flag, user, store) | ||
if value is not None: | ||
return value, prereq_events | ||
|
||
if 'offVariation' in flag and flag['offVariation']: | ||
value = _get_variation(flag, flag['offVariation']) | ||
return value, prereq_events | ||
return None, prereq_events | ||
|
||
|
||
def _evaluate(flag, user, store, prereq_events=None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we had the default value for prereq_events set to [] it was getting reused across function calls and the array just kept getting appended and we sent back a ton of prereq events. |
||
events = prereq_events or [] | ||
failed_prereq = None | ||
prereq_value = None | ||
for prereq in flag.get('prerequisites') or []: | ||
prereq_flag = store.get(prereq.get('key')) | ||
if prereq_flag is None: | ||
log.warn("Missing prereq flag: " + prereq.get('key')) | ||
failed_prereq = prereq | ||
break | ||
if prereq_flag.get('on', False) is True: | ||
prereq_value, prereq_events = evaluate(prereq_flag, user, store, prereq_events) | ||
event = {'kind': 'feature', 'key': prereq.get('key'), 'user': user, 'value': prereq_value} | ||
prereq_events.append(event) | ||
prereq_value, events = _evaluate(prereq_flag, user, store, events) | ||
variation = _get_variation(prereq_flag, prereq.get('variation')) | ||
if prereq_value is None or not prereq_value == variation: | ||
failed_prereq = prereq | ||
else: | ||
failed_prereq = prereq | ||
|
||
event = {'kind': 'feature', 'key': prereq.get('key'), 'user': user, | ||
'value': prereq_value, 'version': prereq_flag.get('version'), 'prereqOf': prereq.get('key')} | ||
events.append(event) | ||
|
||
if failed_prereq is not None: | ||
return None, prereq_events | ||
return None, events | ||
|
||
index = _evaluate_index(flag, user) | ||
return _get_variation(flag, index), prereq_events | ||
return _get_variation(flag, index), events | ||
|
||
|
||
def _evaluate_index(feature, user): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change api_key to
sdk_key
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a wholesale change- including removing the api_key prefix from the auth header.