Skip to content

Commit 68f6363

Browse files
prepare 7.1.0 release (#155)
1 parent 6788933 commit 68f6363

File tree

8 files changed

+102
-5
lines changed

8 files changed

+102
-5
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ jobs:
4949
- run:
5050
name: install requirements
5151
command: |
52-
sudo pip install --upgrade pip virtualenv;
52+
sudo pip install --upgrade pip;
53+
sudo pip install 'virtualenv~=16.0';
5354
sudo pip install -r test-requirements.txt;
5455
sudo pip install -r test-filesource-optional-requirements.txt;
5556
sudo pip install -r consul-requirements.txt;

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ This major release is for Python compatibility updates and removal of deprecated
2626
- 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))`.
2727
- Removed all other types, parameters, and methods that were deprecated as of the last 6.x release.
2828

29+
## [6.13.3] - 2021-02-23
30+
### Fixed:
31+
- 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.
2932

3033
## [6.13.2] - 2020-09-21
3134
### Fixed:

ldclient/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,25 @@ def track(self, event_name: str, user: dict, data: Optional[Any]=None, metric_va
187187
else:
188188
self._send_event(self._event_factory_default.new_custom_event(event_name, user, data, metric_value))
189189

190+
def alias(self, current_user: dict, previous_user: dict):
191+
"""Associates two users for analytics purposes.
192+
193+
This can be helpful in the situation where a person is represented by multiple
194+
LaunchDarkly users. This may happen, for example, when a person initially logs into
195+
an application, the person might be represented by an anonymous user prior to logging
196+
in and a different user after logging in, as denoted by a different user key.
197+
198+
:param current_user: The new version of a user.
199+
:param previous_user: The old version of a user.
200+
"""
201+
if current_user is None or current_user.get('key') is None:
202+
log.warning("Missing current_user or current_user key when calling alias().")
203+
return None
204+
if previous_user is None or previous_user.get('key') is None:
205+
log.warning("Missing previous_user or previous_user key when calling alias().")
206+
return None
207+
self._send_event(self._event_factory_default.new_alias_event(current_user, previous_user))
208+
190209
def identify(self, user: dict):
191210
"""Registers the user.
192211

ldclient/event_processor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def make_output_event(self, e):
6565
out['userKey'] = self._get_userkey(e)
6666
if e.get('reason'):
6767
out['reason'] = e.get('reason')
68+
if e.get('contextKind'):
69+
out['contextKind'] = e.get('contextKind')
6870
return out
6971
elif kind == 'identify':
7072
return {
@@ -87,6 +89,8 @@ def make_output_event(self, e):
8789
out['data'] = e['data']
8890
if e.get('metricValue') is not None:
8991
out['metricValue'] = e['metricValue']
92+
if e.get('contextKind'):
93+
out['contextKind'] = e.get('contextKind')
9094
return out
9195
elif kind == 'index':
9296
return {

ldclient/impl/event_factory.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def new_eval_event(self, flag, user, detail, default_value, prereq_of_flag = Non
3030
e['prereqOf'] = prereq_of_flag.get('key')
3131
if add_experiment_data or self._with_reasons:
3232
e['reason'] = detail.reason
33+
if user is not None and user.get('anonymous'):
34+
e['contextKind'] = self._user_to_context_kind(user)
3335
return e
3436

3537
def new_default_event(self, flag, user, default_value, reason):
@@ -48,6 +50,8 @@ def new_default_event(self, flag, user, default_value, reason):
4850
e['debugEventsUntilDate'] = flag.get('debugEventsUntilDate')
4951
if self._with_reasons:
5052
e['reason'] = reason
53+
if user is not None and user.get('anonymous'):
54+
e['contextKind'] = self._user_to_context_kind(user)
5155
return e
5256

5357
def new_unknown_flag_event(self, key, user, default_value, reason):
@@ -60,6 +64,8 @@ def new_unknown_flag_event(self, key, user, default_value, reason):
6064
}
6165
if self._with_reasons:
6266
e['reason'] = reason
67+
if user is not None and user.get('anonymous'):
68+
e['contextKind'] = self._user_to_context_kind(user)
6369
return e
6470

6571
def new_identify_event(self, user):
@@ -79,8 +85,25 @@ def new_custom_event(self, event_name, user, data, metric_value):
7985
e['data'] = data
8086
if metric_value is not None:
8187
e['metricValue'] = metric_value
88+
if user.get('anonymous'):
89+
e['contextKind'] = self._user_to_context_kind(user)
8290
return e
8391

92+
def new_alias_event(self, current_user, previous_user):
93+
return {
94+
'kind': 'alias',
95+
'key': current_user.get('key'),
96+
'contextKind': self._user_to_context_kind(current_user),
97+
'previousKey': previous_user.get('key'),
98+
'previousContextKind': self._user_to_context_kind(previous_user)
99+
}
100+
101+
def _user_to_context_kind(self, user):
102+
if user.get('anonymous'):
103+
return "anonymousUser"
104+
else:
105+
return "user"
106+
84107
def _is_experiment(self, flag, reason):
85108
if reason is not None:
86109
kind = reason['kind']
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pyyaml>=3.0,<5.2
2-
watchdog>=0.9
2+
watchdog>=0.9,<1.0

test-requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mock>=2.0.0
22
pytest>=2.8
3-
redis>=2.10.5
4-
boto3>=1.9.71
3+
redis>=2.10.5,<3.0.0
4+
boto3>=1.9.71,<2.0.0
55
coverage>=4.4
66
jsonpickle==0.9.3
77
pytest-cov>=2.4.0

testing/test_ldclient.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
}
2525
}
2626

27+
anonymous_user = {
28+
u'key': u'abc',
29+
u'anonymous': True
30+
}
2731

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

174178

179+
def test_track_anonymous_user():
180+
with make_client() as client:
181+
client.track('my_event', anonymous_user)
182+
e = get_first_event(client)
183+
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'
184+
185+
186+
def test_alias():
187+
with make_client() as client:
188+
client.alias(user, anonymous_user)
189+
e = get_first_event(client)
190+
assert e['kind'] == 'alias' and e['key'] == 'xyz' and e['contextKind'] == 'user' and e['previousKey'] == 'abc' and e['previousContextKind'] == 'anonymousUser'
191+
192+
193+
def test_alias_no_user():
194+
with make_client() as client:
195+
client.alias(None, None)
196+
assert count_events(client) == 0
197+
198+
175199
def test_defaults():
176200
config=Config("SDK_KEY", base_uri="http://localhost:3000", defaults={"foo": "bar"}, offline=True)
177201
with LDClient(config=config) as client:
@@ -226,7 +250,30 @@ def test_event_for_existing_feature():
226250
e.get('reason') is None and
227251
e['default'] == 'default' and
228252
e['trackEvents'] == True and
229-
e['debugEventsUntilDate'] == 1000)
253+
e['debugEventsUntilDate'] == 1000 and
254+
e.get('contextKind') is None)
255+
256+
257+
def test_event_for_existing_feature_anonymous_user():
258+
feature = make_off_flag_with_value('feature.key', 'value')
259+
feature['trackEvents'] = True
260+
feature['debugEventsUntilDate'] = 1000
261+
store = InMemoryFeatureStore()
262+
store.init({FEATURES: {'feature.key': feature}})
263+
with make_client(store) as client:
264+
assert 'value' == client.variation('feature.key', anonymous_user, default='default')
265+
e = get_first_event(client)
266+
assert (e['kind'] == 'feature' and
267+
e['key'] == 'feature.key' and
268+
e['user'] == anonymous_user and
269+
e['version'] == feature['version'] and
270+
e['value'] == 'value' and
271+
e['variation'] == 0 and
272+
e.get('reason') is None and
273+
e['default'] == 'default' and
274+
e['trackEvents'] == True and
275+
e['debugEventsUntilDate'] == 1000 and
276+
e['contextKind'] == 'anonymousUser')
230277

231278

232279
def test_event_for_existing_feature_with_reason():

0 commit comments

Comments
 (0)