devcycle-python-server-sdk 2.0.1__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. devcycle_python_sdk/VERSION.txt +1 -1
  2. devcycle_python_sdk/__init__.py +3 -3
  3. devcycle_python_sdk/api/backoff.py +11 -0
  4. devcycle_python_sdk/api/bucketing_client.py +17 -25
  5. devcycle_python_sdk/api/config_client.py +116 -0
  6. devcycle_python_sdk/api/event_client.py +95 -0
  7. devcycle_python_sdk/api/local_bucketing.py +429 -2
  8. devcycle_python_sdk/{dvc_cloud_client.py → cloud_client.py} +69 -41
  9. devcycle_python_sdk/exceptions.py +25 -3
  10. devcycle_python_sdk/local_client.py +220 -0
  11. devcycle_python_sdk/managers/config_manager.py +83 -15
  12. devcycle_python_sdk/managers/event_queue_manager.py +213 -8
  13. devcycle_python_sdk/models/bucketed_config.py +150 -0
  14. devcycle_python_sdk/models/event.py +111 -8
  15. devcycle_python_sdk/models/feature.py +12 -0
  16. devcycle_python_sdk/models/platform_data.py +33 -0
  17. devcycle_python_sdk/models/user.py +52 -7
  18. devcycle_python_sdk/models/variable.py +17 -5
  19. devcycle_python_sdk/options.py +106 -0
  20. devcycle_python_sdk/protobuf/__init__.py +0 -0
  21. devcycle_python_sdk/protobuf/utils.py +129 -0
  22. devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +46 -0
  23. devcycle_python_sdk/util/version.py +1 -1
  24. {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/METADATA +3 -1
  25. devcycle_python_server_sdk-3.0.0.dist-info/RECORD +55 -0
  26. example/{example.py → cloud_client_example.py} +24 -20
  27. example/local_bucketing_client_example.py +74 -0
  28. test/api/test_bucketing_client.py +10 -5
  29. test/api/test_config_client.py +133 -0
  30. test/api/test_event_client.py +97 -0
  31. test/api/test_local_bucketing.py +379 -4
  32. test/fixture/__init__.py +0 -0
  33. test/fixture/data.py +57 -0
  34. test/managers/test_config_manager.py +120 -5
  35. test/managers/test_event_queue_manager.py +259 -5
  36. test/models/__init__.py +0 -0
  37. test/models/test_bucketed_config.py +26 -0
  38. test/{test_dvc_cloud_client.py → test_cloud_client.py} +11 -17
  39. test/test_local_client.py +397 -0
  40. test/util/test_utils.py +183 -0
  41. test/util/test_version.py +2 -2
  42. devcycle_python_sdk/dvc_local_client.py +0 -152
  43. devcycle_python_sdk/dvc_options.py +0 -45
  44. devcycle_python_server_sdk-2.0.1.dist-info/RECORD +0 -41
  45. test/test_bucketing_client.py +0 -228
  46. test/test_dvc_local_client.py +0 -143
  47. test/test_version.py +0 -18
  48. {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/LICENSE +0 -0
  49. {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/WHEEL +0 -0
  50. {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,397 @@
1
+ import logging
2
+ import time
3
+ import unittest
4
+ import uuid
5
+ from unittest.mock import patch
6
+
7
+ import responses
8
+
9
+ from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
10
+ from devcycle_python_sdk.local_client import _validate_user, _validate_sdk_key
11
+ from devcycle_python_sdk.exceptions import MalformedConfigError
12
+ from devcycle_python_sdk.models.event import DevCycleEvent
13
+ from devcycle_python_sdk.models.feature import Feature
14
+ from devcycle_python_sdk.api.local_bucketing import LocalBucketing
15
+ from devcycle_python_sdk.models.user import DevCycleUser
16
+ from devcycle_python_sdk.models.variable import Variable, TypeEnum
17
+ from test.fixture.data import small_config_json
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class DevCycleLocalClientTest(unittest.TestCase):
23
+ def setUp(self) -> None:
24
+ self.sdk_key = "dvc_server_949e4962-c624-4d20-a1ea-7f2501b2ba79"
25
+ self.test_config_json = small_config_json()
26
+ self.test_etag = "2f71454e-3279-4ca7-a8e7-802ce97bef43"
27
+
28
+ config_url = "http://localhost/config/v1/server/" + self.sdk_key + ".json"
29
+
30
+ responses.add(
31
+ responses.GET,
32
+ config_url,
33
+ headers={"ETag": self.test_etag},
34
+ json=self.test_config_json,
35
+ status=200,
36
+ )
37
+
38
+ self.options = DevCycleLocalOptions(
39
+ config_polling_interval_ms=5000,
40
+ config_cdn_uri="http://localhost/",
41
+ disable_custom_event_logging=True,
42
+ disable_automatic_event_logging=True,
43
+ )
44
+ self.test_user = DevCycleUser(user_id="test_user_id")
45
+ self.test_user_empty_id = DevCycleUser(user_id="")
46
+ self.client = None
47
+
48
+ def tearDown(self) -> None:
49
+ if self.client:
50
+ self.client.close()
51
+ responses.reset()
52
+
53
+ def setup_client(self):
54
+ self.client = DevCycleLocalClient(self.sdk_key, self.options)
55
+ while not self.client.is_initialized():
56
+ time.sleep(0.05)
57
+
58
+ def test_validate_sdk_key(self):
59
+ with self.assertRaises(ValueError):
60
+ _validate_sdk_key(None)
61
+
62
+ with self.assertRaises(ValueError):
63
+ _validate_sdk_key("")
64
+
65
+ with self.assertRaises(ValueError):
66
+ _validate_sdk_key("client_" + str(uuid.uuid4()))
67
+
68
+ with self.assertRaises(ValueError):
69
+ _validate_sdk_key(str(uuid.uuid4()))
70
+
71
+ def test_validate_user(self):
72
+ with self.assertRaises(ValueError):
73
+ _validate_user(None)
74
+
75
+ with self.assertRaises(ValueError):
76
+ _validate_user(self.test_user_empty_id)
77
+
78
+ @responses.activate
79
+ def test_create_client_invalid_sdk_key(self):
80
+ with self.assertRaises(ValueError):
81
+ DevCycleLocalClient(None, None)
82
+
83
+ with self.assertRaises(ValueError):
84
+ DevCycleLocalClient("", None)
85
+
86
+ with self.assertRaises(ValueError):
87
+ DevCycleLocalClient("no prefix in key", None)
88
+
89
+ @responses.activate
90
+ def test_variable_bad_user(self):
91
+ self.setup_client()
92
+
93
+ with self.assertRaises(ValueError):
94
+ self.client.variable(None, "strKey", "default_value")
95
+
96
+ with self.assertRaises(ValueError):
97
+ self.client.variable(self.test_user_empty_id, "strKey", "default_value")
98
+
99
+ @responses.activate
100
+ def test_variable_bad_key_and_value(self):
101
+ self.setup_client()
102
+
103
+ with self.assertRaises(ValueError):
104
+ self.client.variable(self.test_user, None, "default_value")
105
+
106
+ with self.assertRaises(ValueError):
107
+ self.client.variable(self.test_user, "", "default_value")
108
+
109
+ with self.assertRaises(ValueError):
110
+ self.client.variable(self.test_user, "strKey", None)
111
+
112
+ @responses.activate
113
+ def test_all_variables_bad_user(self):
114
+ self.setup_client()
115
+
116
+ with self.assertRaises(ValueError):
117
+ self.client.all_variables(None)
118
+
119
+ with self.assertRaises(ValueError):
120
+ self.client.all_variables(self.test_user_empty_id)
121
+
122
+ @responses.activate
123
+ def test_all_features_bad_user(self):
124
+ self.setup_client()
125
+
126
+ with self.assertRaises(ValueError):
127
+ self.client.all_features(None)
128
+
129
+ with self.assertRaises(ValueError):
130
+ self.client.all_features(self.test_user_empty_id)
131
+
132
+ @responses.activate
133
+ def test_track_event_bad_user(self):
134
+ self.setup_client()
135
+
136
+ event = DevCycleEvent(
137
+ type="user",
138
+ target="test_target",
139
+ value=42,
140
+ metaData={"key": "value"},
141
+ )
142
+
143
+ with self.assertRaises(ValueError):
144
+ self.client.track(None, event)
145
+
146
+ with self.assertRaises(ValueError):
147
+ self.client.track(self.test_user_empty_id, event)
148
+
149
+ @responses.activate
150
+ def test_track_bad_event(self):
151
+ self.setup_client()
152
+
153
+ with self.assertRaises(ValueError):
154
+ self.client.track(self.test_user, None)
155
+
156
+ event = DevCycleEvent(
157
+ type=None,
158
+ target="test_target",
159
+ value=42,
160
+ metaData={"key": "value"},
161
+ )
162
+ with self.assertRaises(ValueError):
163
+ self.client.track(self.test_user, event)
164
+
165
+ event = DevCycleEvent(
166
+ type="",
167
+ target="test_target",
168
+ value=42,
169
+ metaData={"key": "value"},
170
+ )
171
+ with self.assertRaises(ValueError):
172
+ self.client.track(self.test_user, event)
173
+
174
+ @responses.activate
175
+ def test_set_client_custom_data(self):
176
+ self.setup_client()
177
+
178
+ # set the data without error
179
+ client_custom_data = {
180
+ "strProp": "strVal",
181
+ "intProp": 1,
182
+ "floatProp": 1.1,
183
+ "boolProp": True,
184
+ "nullProp": None,
185
+ }
186
+ self.client.set_client_custom_data(client_custom_data)
187
+
188
+ @responses.activate
189
+ def test_variable_default(self):
190
+ self.setup_client()
191
+
192
+ # try each default value data type and make sure it gets returned
193
+ tests = [
194
+ ("default_value", TypeEnum.STRING),
195
+ (True, TypeEnum.BOOLEAN),
196
+ (1000, TypeEnum.NUMBER),
197
+ (0.0001, TypeEnum.NUMBER),
198
+ ({"key": "value"}, TypeEnum.JSON),
199
+ ]
200
+
201
+ for test_value, value_type in tests:
202
+ result = self.client.variable(self.test_user, "badKey", test_value)
203
+ self.assertIsNotNone(result)
204
+ self.assertEqual(result.key, "badKey")
205
+ self.assertTrue(result.isDefaulted)
206
+ self.assertEqual(result.defaultValue, test_value)
207
+ self.assertEqual(result.value, test_value)
208
+ self.assertEqual(result.type, value_type)
209
+
210
+ @responses.activate
211
+ def test_variable_with_bucketing(self):
212
+ self.setup_client()
213
+
214
+ user = DevCycleUser(user_id="1234")
215
+
216
+ tests = [
217
+ ("string-var", "default_value", "variationOn", TypeEnum.STRING),
218
+ ("a-cool-new-feature", False, True, TypeEnum.BOOLEAN),
219
+ ("num-var", 0, 12345, TypeEnum.NUMBER),
220
+ (
221
+ "json-var",
222
+ {"default": "value"},
223
+ {
224
+ "displayText": "This variation is on",
225
+ "showDialog": True,
226
+ "maxUsers": 100,
227
+ },
228
+ TypeEnum.JSON,
229
+ ),
230
+ ]
231
+
232
+ for key, default_val, expected, var_type in tests:
233
+ result = self.client.variable(user, key, default_val)
234
+ self.assertIsNotNone(result, msg="Test key: " + key)
235
+ self.assertEqual(result.key, key, msg="Test key: " + key)
236
+ self.assertFalse(result.isDefaulted, msg="Test key: " + key)
237
+ if var_type == TypeEnum.JSON:
238
+ self.assertDictEqual(result.value, expected, msg="Test key: " + key)
239
+ else:
240
+ self.assertEqual(result.value, expected, msg="Test key: " + key)
241
+
242
+ @responses.activate
243
+ def test_variable_with_events(self):
244
+ self.options.disable_automatic_event_logging = False
245
+ self.options.disable_custom_event_logging = False
246
+ self.options.event_flush_interval_ms = 0
247
+ self.setup_client()
248
+ user = DevCycleUser(user_id="1234")
249
+ self.client.variable(user, "string-var", "default_value")
250
+
251
+ @responses.activate
252
+ def test_all_features(self):
253
+ self.setup_client()
254
+
255
+ user = DevCycleUser(user_id="1234")
256
+
257
+ result = self.client.all_features(user)
258
+ self.assertEqual(
259
+ result,
260
+ {
261
+ "a-cool-new-feature": Feature(
262
+ _id="62fbf6566f1ba302829f9e32",
263
+ key="a-cool-new-feature",
264
+ type="release",
265
+ _variation="62fbf6566f1ba302829f9e39",
266
+ variationName="VariationOn",
267
+ variationKey="variation-on",
268
+ evalReason=None,
269
+ )
270
+ },
271
+ )
272
+
273
+ @responses.activate
274
+ @patch.object(
275
+ LocalBucketing,
276
+ "generate_bucketed_config",
277
+ side_effect=MalformedConfigError("bad config"),
278
+ )
279
+ def test_all_features_exception(self, _):
280
+ self.setup_client()
281
+
282
+ user = DevCycleUser(user_id="1234")
283
+
284
+ result = self.client.all_features(user)
285
+ self.assertEqual(result, {})
286
+
287
+ @responses.activate
288
+ def test_all_variables(self):
289
+ self.setup_client()
290
+
291
+ user = DevCycleUser(user_id="1234")
292
+
293
+ result = self.client.all_variables(user)
294
+
295
+ expected_variables = {
296
+ "a-cool-new-feature": Variable(
297
+ _id="62fbf6566f1ba302829f9e34",
298
+ key="a-cool-new-feature",
299
+ type="Boolean",
300
+ value=True,
301
+ isDefaulted=None,
302
+ defaultValue=None,
303
+ evalReason=None,
304
+ ),
305
+ "string-var": Variable(
306
+ _id="63125320a4719939fd57cb2b",
307
+ key="string-var",
308
+ type="String",
309
+ value="variationOn",
310
+ isDefaulted=None,
311
+ defaultValue=None,
312
+ evalReason=None,
313
+ ),
314
+ "json-var": Variable(
315
+ _id="64372363125123fca69d3f7b",
316
+ key="json-var",
317
+ type="JSON",
318
+ value={
319
+ "displayText": "This variation is on",
320
+ "showDialog": True,
321
+ "maxUsers": 100,
322
+ },
323
+ isDefaulted=None,
324
+ defaultValue=None,
325
+ evalReason=None,
326
+ ),
327
+ "num-var": Variable(
328
+ _id="65272363125123fca69d3a7d",
329
+ key="num-var",
330
+ type="Number",
331
+ value=12345,
332
+ isDefaulted=None,
333
+ defaultValue=None,
334
+ evalReason=None,
335
+ ),
336
+ }
337
+ self.assertEqual(result, expected_variables)
338
+
339
+ @responses.activate
340
+ @patch.object(
341
+ LocalBucketing,
342
+ "generate_bucketed_config",
343
+ side_effect=MalformedConfigError("bad config"),
344
+ )
345
+ def test_all_variables_exception(self, _):
346
+ self.setup_client()
347
+
348
+ user = DevCycleUser(user_id="1234")
349
+
350
+ result = self.client.all_variables(user)
351
+ self.assertEqual(result, {})
352
+
353
+
354
+ def _benchmark_variable_call(client: DevCycleLocalClient, user: DevCycleUser, key: str):
355
+ return client.variable(user, key, "default_value")
356
+
357
+
358
+ @responses.activate
359
+ def test_benchmark_variable_call(benchmark):
360
+ sdk_key = "dvc_server_949e4962-c624-4d20-a1ea-7f2501b2ba79"
361
+ test_config_json = small_config_json()
362
+ test_etag = "2f71454e-3279-4ca7-a8e7-802ce97bef43"
363
+
364
+ config_url = "http://localhost/config/v1/server/" + sdk_key + ".json"
365
+
366
+ responses.add(
367
+ responses.GET,
368
+ config_url,
369
+ headers={"ETag": test_etag},
370
+ json=test_config_json,
371
+ status=200,
372
+ )
373
+
374
+ options = DevCycleLocalOptions(
375
+ config_polling_interval_ms=5000,
376
+ config_cdn_uri="http://localhost/",
377
+ disable_custom_event_logging=True,
378
+ disable_automatic_event_logging=True,
379
+ )
380
+ user = DevCycleUser(user_id="test_user_id")
381
+
382
+ client = DevCycleLocalClient(sdk_key, options)
383
+ while not client.is_initialized():
384
+ time.sleep(0.05)
385
+
386
+ # benchmark is a pytest fixture provided by pytest-benchmark that handles timing the provided callable
387
+ result = benchmark(_benchmark_variable_call, client, user, "string-var")
388
+
389
+ assert result is not None
390
+ assert result.key == "string-var"
391
+ assert result.isDefaulted is False
392
+ assert result.value == "variationOn"
393
+ assert result.type == TypeEnum.STRING
394
+
395
+
396
+ if __name__ == "__main__":
397
+ unittest.main()
@@ -0,0 +1,183 @@
1
+ import logging
2
+
3
+ import unittest
4
+
5
+ from devcycle_python_sdk.models.variable import TypeEnum
6
+ from devcycle_python_sdk.models.user import DevCycleUser
7
+ import devcycle_python_sdk.protobuf.utils as utils
8
+ import devcycle_python_sdk.protobuf.variableForUserParams_pb2 as pb2
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class VersionTest(unittest.TestCase):
14
+ def test_create_nullable_double(self):
15
+ result = utils.create_nullable_double(None)
16
+ self.assertIsNotNone(result)
17
+ self.assertTrue(result.isNull)
18
+
19
+ result = utils.create_nullable_double(99.0)
20
+ self.assertIsNotNone(result)
21
+ self.assertFalse(result.isNull)
22
+ self.assertEqual(result.value, 99.0)
23
+
24
+ def test_create_nullable_string(self):
25
+ result = utils.create_nullable_string(None)
26
+ self.assertIsNotNone(result)
27
+ self.assertTrue(result.isNull)
28
+
29
+ result = utils.create_nullable_string("")
30
+ self.assertIsNotNone(result)
31
+ self.assertFalse(result.isNull)
32
+ self.assertEqual(result.value, "")
33
+
34
+ result = utils.create_nullable_string("test value")
35
+ self.assertIsNotNone(result)
36
+ self.assertFalse(result.isNull)
37
+ self.assertEqual(result.value, "test value")
38
+
39
+ def test_create_nullable_custom_data(self):
40
+ result = utils.create_nullable_custom_data(None)
41
+ self.assertIsNotNone(result)
42
+ self.assertTrue(result.isNull)
43
+
44
+ result = utils.create_nullable_custom_data(dict())
45
+ self.assertIsNotNone(result)
46
+ self.assertTrue(result.isNull)
47
+
48
+ result = utils.create_nullable_custom_data({"strProp": "test value"})
49
+ self.assertIsNotNone(result)
50
+ self.assertFalse(result.isNull)
51
+ self.assertEqual(result.value["strProp"].type, pb2.CustomDataType.Str)
52
+ self.assertEqual(result.value["strProp"].stringValue, "test value")
53
+
54
+ result = utils.create_nullable_custom_data({"boolProp": False})
55
+ self.assertIsNotNone(result)
56
+ self.assertFalse(result.isNull)
57
+ self.assertEqual(result.value["boolProp"].type, pb2.CustomDataType.Bool)
58
+ self.assertEqual(result.value["boolProp"].boolValue, False)
59
+
60
+ result = utils.create_nullable_custom_data({"numProp": 1234.0})
61
+ self.assertIsNotNone(result)
62
+ self.assertFalse(result.isNull)
63
+ self.assertEqual(result.value["numProp"].type, pb2.CustomDataType.Num)
64
+ self.assertEqual(result.value["numProp"].doubleValue, 1234.0)
65
+
66
+ def test_create_variable_string(self):
67
+ sdk_var = pb2.SDKVariable_PB(
68
+ _id="test id",
69
+ key="test key",
70
+ stringValue="test value",
71
+ type=pb2.VariableType_PB.String,
72
+ )
73
+
74
+ var = utils.create_variable(sdk_var, "default value")
75
+ self.assertIsNotNone(var)
76
+ self.assertEqual(var.type, TypeEnum.STRING)
77
+ self.assertIsNone(var._id)
78
+ self.assertEqual(var.key, sdk_var.key)
79
+ self.assertEqual(var.value, sdk_var.stringValue)
80
+ self.assertEqual(var.defaultValue, "default value")
81
+ self.assertFalse(var.isDefaulted)
82
+
83
+ def test_create_variable_boolean(self):
84
+ sdk_var = pb2.SDKVariable_PB(
85
+ _id="test id",
86
+ key="test key",
87
+ boolValue=True,
88
+ type=pb2.VariableType_PB.Boolean,
89
+ )
90
+
91
+ var = utils.create_variable(sdk_var, False)
92
+ self.assertIsNotNone(var)
93
+ self.assertEqual(var.type, TypeEnum.BOOLEAN)
94
+ self.assertIsNone(var._id)
95
+ self.assertEqual(var.key, sdk_var.key)
96
+ self.assertEqual(var.value, sdk_var.boolValue)
97
+ self.assertEqual(var.defaultValue, False)
98
+ self.assertFalse(var.isDefaulted)
99
+
100
+ def test_create_variable_number(self):
101
+ sdk_var = pb2.SDKVariable_PB(
102
+ _id="test id",
103
+ key="test key",
104
+ doubleValue=99.99,
105
+ type=pb2.VariableType_PB.Number,
106
+ )
107
+
108
+ var = utils.create_variable(sdk_var, 0)
109
+ self.assertIsNotNone(var)
110
+ self.assertEqual(var.type, TypeEnum.NUMBER)
111
+ self.assertIsNone(var._id)
112
+ self.assertEqual(var.key, sdk_var.key)
113
+ self.assertEqual(var.value, sdk_var.doubleValue)
114
+ self.assertEqual(var.defaultValue, 0)
115
+ self.assertFalse(var.isDefaulted)
116
+
117
+ def test_create_variable_json(self):
118
+ sdk_var = pb2.SDKVariable_PB(
119
+ _id="test id",
120
+ key="test key",
121
+ stringValue='{"strProp": "test value"}',
122
+ type=pb2.VariableType_PB.JSON,
123
+ )
124
+
125
+ var = utils.create_variable(sdk_var, {})
126
+ self.assertIsNotNone(var)
127
+ self.assertEqual(var.type, TypeEnum.JSON)
128
+ self.assertIsNone(var._id)
129
+ self.assertEqual(var.key, sdk_var.key)
130
+ self.assertDictEqual(var.value, {"strProp": "test value"})
131
+ self.assertDictEqual(var.defaultValue, {})
132
+ self.assertFalse(var.isDefaulted)
133
+
134
+ def test_create_dvcuser_pb_bad_app_build(self):
135
+ user = DevCycleUser(
136
+ user_id="test id",
137
+ appBuild=None,
138
+ )
139
+
140
+ result = utils.create_dvcuser_pb(user)
141
+ self.assertIsNotNone(result)
142
+ self.assertEqual(result.user_id, user.user_id)
143
+ self.assertTrue(result.appBuild.isNull)
144
+
145
+ user = DevCycleUser(
146
+ user_id="test id",
147
+ appBuild="NotANumberAtAll",
148
+ )
149
+
150
+ result = utils.create_dvcuser_pb(user)
151
+ self.assertIsNotNone(result)
152
+ self.assertEqual(result.user_id, user.user_id)
153
+ self.assertTrue(result.appBuild.isNull)
154
+
155
+ def test_create_dvcuser_pb(self):
156
+ user = DevCycleUser(
157
+ user_id="test id",
158
+ name="test name",
159
+ email="test email",
160
+ customData={"a": "value1", "b": 0.0, "c": False},
161
+ privateCustomData={"x": "value2", "y": 1234.0, "z": False},
162
+ appBuild="1.17",
163
+ appVersion="1.0.0",
164
+ country="US",
165
+ language=None,
166
+ deviceModel="iPhone X",
167
+ )
168
+
169
+ result = utils.create_dvcuser_pb(user)
170
+ self.assertIsNotNone(result)
171
+ self.assertEqual(result.user_id, user.user_id)
172
+ self.assertEqual(result.name.value, user.name)
173
+ self.assertEqual(result.email.value, user.email)
174
+ self.assertTrue(result.language.isNull)
175
+ self.assertEqual(result.appBuild.value, 1.17)
176
+
177
+ self.assertEqual(result.customData.value["a"].type, pb2.CustomDataType.Str)
178
+ self.assertEqual(result.customData.value["a"].stringValue, "value1")
179
+
180
+ self.assertEqual(
181
+ result.privateCustomData.value["x"].type, pb2.CustomDataType.Str
182
+ )
183
+ self.assertEqual(result.privateCustomData.value["x"].stringValue, "value2")
test/util/test_version.py CHANGED
@@ -9,10 +9,10 @@ logger = logging.getLogger(__name__)
9
9
 
10
10
  class VersionTest(unittest.TestCase):
11
11
  def test_sdk_version(self):
12
- expected_version = "2.0.1"
12
+ expected_version = "3.0.0"
13
13
  version = sdk_version()
14
14
  self.assertEqual(version, expected_version)
15
15
 
16
16
 
17
- if __name__ == '__main__':
17
+ if __name__ == "__main__":
18
18
  unittest.main()