Skip to content

Commit

Permalink
prepare 7.1.0 release (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaunchDarklyCI authored Mar 12, 2021
1 parent 6788933 commit 68f6363
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ jobs:
- run:
name: install requirements
command: |
sudo pip install --upgrade pip virtualenv;
sudo pip install --upgrade pip;
sudo pip install 'virtualenv~=16.0';
sudo pip install -r test-requirements.txt;
sudo pip install -r test-filesource-optional-requirements.txt;
sudo pip install -r consul-requirements.txt;
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ This major release is for Python compatibility updates and removal of deprecated
- Removed the individual HTTP-related parameters such as `connect_timeout` from the [`Config`](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.config.Config) type. The correct way to set these now is with the [`HTTPConfig`](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.config.HTTPConfig) sub-configuration object: `Config(sdk_key = "my-sdk-key", http = HTTPConfig(connect_timeout = 10))`.
- Removed all other types, parameters, and methods that were deprecated as of the last 6.x release.

## [6.13.3] - 2021-02-23
### Fixed:
- The SDK could fail to send debug events when event debugging was enabled on the LaunchDarkly dashboard, if the application server's time zone was not GMT.

## [6.13.2] - 2020-09-21
### Fixed:
Expand Down
19 changes: 19 additions & 0 deletions ldclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,25 @@ def track(self, event_name: str, user: dict, data: Optional[Any]=None, metric_va
else:
self._send_event(self._event_factory_default.new_custom_event(event_name, user, data, metric_value))

def alias(self, current_user: dict, previous_user: dict):
"""Associates two users for analytics purposes.
This can be helpful in the situation where a person is represented by multiple
LaunchDarkly users. This may happen, for example, when a person initially logs into
an application, the person might be represented by an anonymous user prior to logging
in and a different user after logging in, as denoted by a different user key.
:param current_user: The new version of a user.
:param previous_user: The old version of a user.
"""
if current_user is None or current_user.get('key') is None:
log.warning("Missing current_user or current_user key when calling alias().")
return None
if previous_user is None or previous_user.get('key') is None:
log.warning("Missing previous_user or previous_user key when calling alias().")
return None
self._send_event(self._event_factory_default.new_alias_event(current_user, previous_user))

def identify(self, user: dict):
"""Registers the user.
Expand Down
4 changes: 4 additions & 0 deletions ldclient/event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def make_output_event(self, e):
out['userKey'] = self._get_userkey(e)
if e.get('reason'):
out['reason'] = e.get('reason')
if e.get('contextKind'):
out['contextKind'] = e.get('contextKind')
return out
elif kind == 'identify':
return {
Expand All @@ -87,6 +89,8 @@ def make_output_event(self, e):
out['data'] = e['data']
if e.get('metricValue') is not None:
out['metricValue'] = e['metricValue']
if e.get('contextKind'):
out['contextKind'] = e.get('contextKind')
return out
elif kind == 'index':
return {
Expand Down
23 changes: 23 additions & 0 deletions ldclient/impl/event_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def new_eval_event(self, flag, user, detail, default_value, prereq_of_flag = Non
e['prereqOf'] = prereq_of_flag.get('key')
if add_experiment_data or self._with_reasons:
e['reason'] = detail.reason
if user is not None and user.get('anonymous'):
e['contextKind'] = self._user_to_context_kind(user)
return e

def new_default_event(self, flag, user, default_value, reason):
Expand All @@ -48,6 +50,8 @@ def new_default_event(self, flag, user, default_value, reason):
e['debugEventsUntilDate'] = flag.get('debugEventsUntilDate')
if self._with_reasons:
e['reason'] = reason
if user is not None and user.get('anonymous'):
e['contextKind'] = self._user_to_context_kind(user)
return e

def new_unknown_flag_event(self, key, user, default_value, reason):
Expand All @@ -60,6 +64,8 @@ def new_unknown_flag_event(self, key, user, default_value, reason):
}
if self._with_reasons:
e['reason'] = reason
if user is not None and user.get('anonymous'):
e['contextKind'] = self._user_to_context_kind(user)
return e

def new_identify_event(self, user):
Expand All @@ -79,8 +85,25 @@ def new_custom_event(self, event_name, user, data, metric_value):
e['data'] = data
if metric_value is not None:
e['metricValue'] = metric_value
if user.get('anonymous'):
e['contextKind'] = self._user_to_context_kind(user)
return e

def new_alias_event(self, current_user, previous_user):
return {
'kind': 'alias',
'key': current_user.get('key'),
'contextKind': self._user_to_context_kind(current_user),
'previousKey': previous_user.get('key'),
'previousContextKind': self._user_to_context_kind(previous_user)
}

def _user_to_context_kind(self, user):
if user.get('anonymous'):
return "anonymousUser"
else:
return "user"

def _is_experiment(self, flag, reason):
if reason is not None:
kind = reason['kind']
Expand Down
2 changes: 1 addition & 1 deletion test-filesource-optional-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyyaml>=3.0,<5.2
watchdog>=0.9
watchdog>=0.9,<1.0
4 changes: 2 additions & 2 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mock>=2.0.0
pytest>=2.8
redis>=2.10.5
boto3>=1.9.71
redis>=2.10.5,<3.0.0
boto3>=1.9.71,<2.0.0
coverage>=4.4
jsonpickle==0.9.3
pytest-cov>=2.4.0
Expand Down
49 changes: 48 additions & 1 deletion testing/test_ldclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
}
}

anonymous_user = {
u'key': u'abc',
u'anonymous': True
}

def make_client(store = InMemoryFeatureStore()):
return LDClient(config=Config(sdk_key = 'SDK_KEY',
Expand Down Expand Up @@ -172,6 +176,26 @@ def test_track_no_user_key():
assert count_events(client) == 0


def test_track_anonymous_user():
with make_client() as client:
client.track('my_event', anonymous_user)
e = get_first_event(client)
assert e['kind'] == 'custom' and e['key'] == 'my_event' and e['user'] == anonymous_user and e.get('data') is None and e.get('metricValue') is None and e.get('contextKind') == 'anonymousUser'


def test_alias():
with make_client() as client:
client.alias(user, anonymous_user)
e = get_first_event(client)
assert e['kind'] == 'alias' and e['key'] == 'xyz' and e['contextKind'] == 'user' and e['previousKey'] == 'abc' and e['previousContextKind'] == 'anonymousUser'


def test_alias_no_user():
with make_client() as client:
client.alias(None, None)
assert count_events(client) == 0


def test_defaults():
config=Config("SDK_KEY", base_uri="http://localhost:3000", defaults={"foo": "bar"}, offline=True)
with LDClient(config=config) as client:
Expand Down Expand Up @@ -226,7 +250,30 @@ def test_event_for_existing_feature():
e.get('reason') is None and
e['default'] == 'default' and
e['trackEvents'] == True and
e['debugEventsUntilDate'] == 1000)
e['debugEventsUntilDate'] == 1000 and
e.get('contextKind') is None)


def test_event_for_existing_feature_anonymous_user():
feature = make_off_flag_with_value('feature.key', 'value')
feature['trackEvents'] = True
feature['debugEventsUntilDate'] = 1000
store = InMemoryFeatureStore()
store.init({FEATURES: {'feature.key': feature}})
with make_client(store) as client:
assert 'value' == client.variation('feature.key', anonymous_user, default='default')
e = get_first_event(client)
assert (e['kind'] == 'feature' and
e['key'] == 'feature.key' and
e['user'] == anonymous_user and
e['version'] == feature['version'] and
e['value'] == 'value' and
e['variation'] == 0 and
e.get('reason') is None and
e['default'] == 'default' and
e['trackEvents'] == True and
e['debugEventsUntilDate'] == 1000 and
e['contextKind'] == 'anonymousUser')


def test_event_for_existing_feature_with_reason():
Expand Down

0 comments on commit 68f6363

Please sign in to comment.