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.
- devcycle_python_sdk/VERSION.txt +1 -1
- devcycle_python_sdk/__init__.py +3 -3
- devcycle_python_sdk/api/backoff.py +11 -0
- devcycle_python_sdk/api/bucketing_client.py +17 -25
- devcycle_python_sdk/api/config_client.py +116 -0
- devcycle_python_sdk/api/event_client.py +95 -0
- devcycle_python_sdk/api/local_bucketing.py +429 -2
- devcycle_python_sdk/{dvc_cloud_client.py → cloud_client.py} +69 -41
- devcycle_python_sdk/exceptions.py +25 -3
- devcycle_python_sdk/local_client.py +220 -0
- devcycle_python_sdk/managers/config_manager.py +83 -15
- devcycle_python_sdk/managers/event_queue_manager.py +213 -8
- devcycle_python_sdk/models/bucketed_config.py +150 -0
- devcycle_python_sdk/models/event.py +111 -8
- devcycle_python_sdk/models/feature.py +12 -0
- devcycle_python_sdk/models/platform_data.py +33 -0
- devcycle_python_sdk/models/user.py +52 -7
- devcycle_python_sdk/models/variable.py +17 -5
- devcycle_python_sdk/options.py +106 -0
- devcycle_python_sdk/protobuf/__init__.py +0 -0
- devcycle_python_sdk/protobuf/utils.py +129 -0
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +46 -0
- devcycle_python_sdk/util/version.py +1 -1
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/METADATA +3 -1
- devcycle_python_server_sdk-3.0.0.dist-info/RECORD +55 -0
- example/{example.py → cloud_client_example.py} +24 -20
- example/local_bucketing_client_example.py +74 -0
- test/api/test_bucketing_client.py +10 -5
- test/api/test_config_client.py +133 -0
- test/api/test_event_client.py +97 -0
- test/api/test_local_bucketing.py +379 -4
- test/fixture/__init__.py +0 -0
- test/fixture/data.py +57 -0
- test/managers/test_config_manager.py +120 -5
- test/managers/test_event_queue_manager.py +259 -5
- test/models/__init__.py +0 -0
- test/models/test_bucketed_config.py +26 -0
- test/{test_dvc_cloud_client.py → test_cloud_client.py} +11 -17
- test/test_local_client.py +397 -0
- test/util/test_utils.py +183 -0
- test/util/test_version.py +2 -2
- devcycle_python_sdk/dvc_local_client.py +0 -152
- devcycle_python_sdk/dvc_options.py +0 -45
- devcycle_python_server_sdk-2.0.1.dist-info/RECORD +0 -41
- test/test_bucketing_client.py +0 -228
- test/test_dvc_local_client.py +0 -143
- test/test_version.py +0 -18
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/LICENSE +0 -0
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/WHEEL +0 -0
- {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,46 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: variableForUserParams.proto
|
|
4
|
+
"""Generated protocol buffer code."""
|
|
5
|
+
from google.protobuf.internal import builder as _builder
|
|
6
|
+
from google.protobuf import descriptor as _descriptor
|
|
7
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
8
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
9
|
+
# @@protoc_insertion_point(imports)
|
|
10
|
+
|
|
11
|
+
_sym_db = _symbol_database.Default()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bvariableForUserParams.proto\"/\n\x0eNullableString\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"/\n\x0eNullableDouble\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"m\n\x0f\x43ustomDataValue\x12\x1d\n\x04type\x18\x01 \x01(\x0e\x32\x0f.CustomDataType\x12\x11\n\tboolValue\x18\x02 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x03 \x01(\x01\x12\x13\n\x0bstringValue\x18\x04 \x01(\t\"\x93\x01\n\x12NullableCustomData\x12-\n\x05value\x18\x01 \x03(\x0b\x32\x1e.NullableCustomData.ValueEntry\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\x1a>\n\nValueEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1f\n\x05value\x18\x02 \x01(\x0b\x32\x10.CustomDataValue:\x02\x38\x01\"\x9c\x01\n\x18VariableForUserParams_PB\x12\x0e\n\x06sdkKey\x18\x01 \x01(\t\x12\x13\n\x0bvariableKey\x18\x02 \x01(\t\x12&\n\x0cvariableType\x18\x03 \x01(\x0e\x32\x10.VariableType_PB\x12\x19\n\x04user\x18\x04 \x01(\x0b\x32\x0b.DVCUser_PB\x12\x18\n\x10shouldTrackEvent\x18\x05 \x01(\x08\"\xe8\x02\n\nDVCUser_PB\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1e\n\x05\x65mail\x18\x02 \x01(\x0b\x32\x0f.NullableString\x12\x1d\n\x04name\x18\x03 \x01(\x0b\x32\x0f.NullableString\x12!\n\x08language\x18\x04 \x01(\x0b\x32\x0f.NullableString\x12 \n\x07\x63ountry\x18\x05 \x01(\x0b\x32\x0f.NullableString\x12!\n\x08\x61ppBuild\x18\x06 \x01(\x0b\x32\x0f.NullableDouble\x12#\n\nappVersion\x18\x07 \x01(\x0b\x32\x0f.NullableString\x12$\n\x0b\x64\x65viceModel\x18\x08 \x01(\x0b\x32\x0f.NullableString\x12\'\n\ncustomData\x18\t \x01(\x0b\x32\x13.NullableCustomData\x12.\n\x11privateCustomData\x18\n \x01(\x0b\x32\x13.NullableCustomData\"\xac\x01\n\x0eSDKVariable_PB\x12\x0b\n\x03_id\x18\x01 \x01(\t\x12\x1e\n\x04type\x18\x02 \x01(\x0e\x32\x10.VariableType_PB\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x11\n\tboolValue\x18\x04 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x05 \x01(\x01\x12\x13\n\x0bstringValue\x18\x06 \x01(\t\x12#\n\nevalReason\x18\x07 \x01(\x0b\x32\x0f.NullableString*@\n\x0fVariableType_PB\x12\x0b\n\x07\x42oolean\x10\x00\x12\n\n\x06Number\x10\x01\x12\n\n\x06String\x10\x02\x12\x08\n\x04JSON\x10\x03*6\n\x0e\x43ustomDataType\x12\x08\n\x04\x42ool\x10\x00\x12\x07\n\x03Num\x10\x01\x12\x07\n\x03Str\x10\x02\x12\x08\n\x04Null\x10\x03\x42X\n&com.devcycle.sdk.server.local.protobufP\x01Z\x07./proto\xaa\x02\"DevCycle.SDK.Server.Local.Protobufb\x06proto3')
|
|
17
|
+
|
|
18
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
|
19
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'variableForUserParams_pb2', globals())
|
|
20
|
+
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
21
|
+
|
|
22
|
+
DESCRIPTOR._options = None
|
|
23
|
+
DESCRIPTOR._serialized_options = b'\n&com.devcycle.sdk.server.local.protobufP\001Z\007./proto\252\002\"DevCycle.SDK.Server.Local.Protobuf'
|
|
24
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._options = None
|
|
25
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._serialized_options = b'8\001'
|
|
26
|
+
_VARIABLETYPE_PB._serialized_start=1087
|
|
27
|
+
_VARIABLETYPE_PB._serialized_end=1151
|
|
28
|
+
_CUSTOMDATATYPE._serialized_start=1153
|
|
29
|
+
_CUSTOMDATATYPE._serialized_end=1207
|
|
30
|
+
_NULLABLESTRING._serialized_start=31
|
|
31
|
+
_NULLABLESTRING._serialized_end=78
|
|
32
|
+
_NULLABLEDOUBLE._serialized_start=80
|
|
33
|
+
_NULLABLEDOUBLE._serialized_end=127
|
|
34
|
+
_CUSTOMDATAVALUE._serialized_start=129
|
|
35
|
+
_CUSTOMDATAVALUE._serialized_end=238
|
|
36
|
+
_NULLABLECUSTOMDATA._serialized_start=241
|
|
37
|
+
_NULLABLECUSTOMDATA._serialized_end=388
|
|
38
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._serialized_start=326
|
|
39
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._serialized_end=388
|
|
40
|
+
_VARIABLEFORUSERPARAMS_PB._serialized_start=391
|
|
41
|
+
_VARIABLEFORUSERPARAMS_PB._serialized_end=547
|
|
42
|
+
_DVCUSER_PB._serialized_start=550
|
|
43
|
+
_DVCUSER_PB._serialized_end=910
|
|
44
|
+
_SDKVARIABLE_PB._serialized_start=913
|
|
45
|
+
_SDKVARIABLE_PB._serialized_end=1085
|
|
46
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -5,5 +5,5 @@ def sdk_version() -> str:
|
|
|
5
5
|
"""
|
|
6
6
|
Returns the current version of this SDK as a semantic version string
|
|
7
7
|
"""
|
|
8
|
-
with open(os.path.dirname(__file__) +
|
|
8
|
+
with open(os.path.dirname(__file__) + "/../VERSION.txt", "r") as version_file:
|
|
9
9
|
return version_file.read().strip()
|
{devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devcycle-python-server-sdk
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: DevCycle Python SDK
|
|
5
5
|
Home-page: https://github.com/devcycleHQ/python-server-sdk
|
|
6
6
|
Author-email:
|
|
@@ -9,6 +9,8 @@ License-File: LICENSE
|
|
|
9
9
|
Requires-Dist: setuptools (>=21.0.0)
|
|
10
10
|
Requires-Dist: urllib3 (>=1.15.1)
|
|
11
11
|
Requires-Dist: requests (~=2.31.0)
|
|
12
|
+
Requires-Dist: wasmtime (==9.0.0)
|
|
13
|
+
Requires-Dist: protobuf (>=4.23.3)
|
|
12
14
|
|
|
13
15
|
The DevCycle Python SDK used for feature management.
|
|
14
16
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
devcycle_python_sdk/VERSION.txt,sha256=yRY_8h8fKwOQ3Ei92kcXlxj3cvUHp8687KWc4acSkCk,5
|
|
2
|
+
devcycle_python_sdk/__init__.py,sha256=vOoglQ8L7RsHTp-GSa8g7k2AAPrR_obhiDSC09LpG8w,265
|
|
3
|
+
devcycle_python_sdk/cloud_client.py,sha256=k9Vr24ej6oNZV3WkbO8KKCcp0ERTnsvmCySI7-A8QYw,6675
|
|
4
|
+
devcycle_python_sdk/exceptions.py,sha256=Et7fS8pEFbHkFY86T2pJoEUstwjh4_rzhJxVH8fzAns,1053
|
|
5
|
+
devcycle_python_sdk/local_client.py,sha256=fJDW4JVQY2ePKAN9OUeDhuHOhMqGGeKrv4Z7eMpfmZU,8605
|
|
6
|
+
devcycle_python_sdk/options.py,sha256=HSny3e5sXydrCamoTSyNkmkdcuaGjY_9etNPngw9Avc,4525
|
|
7
|
+
devcycle_python_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
devcycle_python_sdk/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
9
|
+
devcycle_python_sdk/api/backoff.py,sha256=F6WYCloaZD9CjNu6mKBMq7K20S8-i2WVU6X05ZRoawE,303
|
|
10
|
+
devcycle_python_sdk/api/bucketing_client.py,sha256=cchUuSngJEgDnhR4HeOsCgjH2gJu-yDbz9HFLDT2Bzk,5074
|
|
11
|
+
devcycle_python_sdk/api/config_client.py,sha256=jtnDmGQBaxOCxF346iDfqjIMKclqdOZiApXeXLDakbs,4226
|
|
12
|
+
devcycle_python_sdk/api/event_client.py,sha256=y1u7HQLTM_FHxiwyZ_2hahnVjYaNv2HuSzlNcIHIiOk,3319
|
|
13
|
+
devcycle_python_sdk/api/local_bucketing.py,sha256=96UNaq18Xnx69xIKBQZLOY8C1jkjsDc0vJvR9Jm2_Qw,16322
|
|
14
|
+
devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
devcycle_python_sdk/managers/config_manager.py,sha256=qv71EMtV0qvHOjkclP2fE54eXe8v_MnehjyIkr-YPsM,3122
|
|
16
|
+
devcycle_python_sdk/managers/event_queue_manager.py,sha256=JvOefMEnK3-zbco5TCGr9vysa0nYmI5Eg58Dg4SDUuU,7909
|
|
17
|
+
devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
devcycle_python_sdk/models/bucketed_config.py,sha256=rSKpxMTF-IbUVQ9gAWrWnb9Q9YF94ATqEFWNWuoQntk,3814
|
|
19
|
+
devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
|
|
20
|
+
devcycle_python_sdk/models/event.py,sha256=-6bhKp72mAgaoSxvhS__G4vvfQ9Psj83fc0Igelzw3o,3713
|
|
21
|
+
devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
|
|
22
|
+
devcycle_python_sdk/models/platform_data.py,sha256=kIFevH5trsxI32qp7BW6glUCpipgpaVBKGqJWzQd5Xo,783
|
|
23
|
+
devcycle_python_sdk/models/user.py,sha256=BGMtVM6shDX7rUfGfBbI55IUEsQ_4qrnmtwTqOcB-8A,2616
|
|
24
|
+
devcycle_python_sdk/models/variable.py,sha256=qp00SyCNyfYiMlTwnDSDJ4ZetRF35JHTbYZQOBxFIqY,1711
|
|
25
|
+
devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
devcycle_python_sdk/protobuf/utils.py,sha256=zi-kVx6yvgS2c04cUobLd2IDc_ZqKkWduay9YcpjoGc,4684
|
|
27
|
+
devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=Zf4-8n67UnulC0Sz90aqLYsXCFEgywOqfiSHuXZomLk,4244
|
|
28
|
+
devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
29
|
+
devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
|
|
30
|
+
example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
example/cloud_client_example.py,sha256=YD8ZsTv3dPYfbFUKraRpRT0VsJGrPwjm8ZCuA5H54yI,2463
|
|
32
|
+
example/local_bucketing_client_example.py,sha256=WRyJOUayO1siwnkfPhXvMg3Oz53akcA-Y4EMBI1e4q0,2676
|
|
33
|
+
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
test/test_cloud_client.py,sha256=zjk302m7qKMCQryolkc1WKLFmbGWUxA0VtHlZk0rY-0,11442
|
|
35
|
+
test/test_local_client.py,sha256=mEBhq0gnHzjE-i_w4hMrmfhoHDJvCXGt3G8lx0EA9OE,12671
|
|
36
|
+
test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
test/api/test_bucketing_client.py,sha256=Zomy0LQ-abI6nZvazSgBZQX6OjPwqBbsmfXR7KpdNOk,7565
|
|
38
|
+
test/api/test_config_client.py,sha256=gV86fwGBFN5r81jrfGKyptldgR5YGEXdUdFlXwFaupg,4275
|
|
39
|
+
test/api/test_event_client.py,sha256=jaWFWY0jrFI0amCfRsByi-JHPkoWKH_Z7V2nNcbvepM,3211
|
|
40
|
+
test/api/test_local_bucketing.py,sha256=E7alurvIScT0nilcuY2xGLCXzwxHrNWPBGdWxG6bR5A,15156
|
|
41
|
+
test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
test/fixture/data.py,sha256=OFHR29FiiMWhh2bmyeEbCck4Jyy8Ah7MsoLg0JZN0vk,1372
|
|
43
|
+
test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
test/managers/test_config_manager.py,sha256=_MtvyxK06xpBzzEsN1zsLdYrk3DsPsbCmSj7XorINmI,5366
|
|
45
|
+
test/managers/test_event_queue_manager.py,sha256=KquHBuYiDdoVStm5Ga9mIB-wvmJnChiFRBNC8sMKaxQ,11053
|
|
46
|
+
test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
+
test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
|
|
48
|
+
test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
|
|
50
|
+
test/util/test_version.py,sha256=4Qhi1grX84lmyQRJ6aQMJgMerl3ce2_jKEl71vhqG5E,368
|
|
51
|
+
devcycle_python_server_sdk-3.0.0.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
|
|
52
|
+
devcycle_python_server_sdk-3.0.0.dist-info/METADATA,sha256=unv6PBvFApXc0qamRTmXPzgRJTFO5qGNllrmCElhSgs,452
|
|
53
|
+
devcycle_python_server_sdk-3.0.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
54
|
+
devcycle_python_server_sdk-3.0.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
|
|
55
|
+
devcycle_python_server_sdk-3.0.0.dist-info/RECORD,,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
3
4
|
|
|
4
5
|
from devcycle_python_sdk import DevCycleCloudClient, DevCycleCloudOptions
|
|
5
|
-
|
|
6
|
-
from devcycle_python_sdk.models.
|
|
6
|
+
|
|
7
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
8
|
+
from devcycle_python_sdk.models.event import DevCycleEvent, EventType
|
|
7
9
|
|
|
8
10
|
VARIABLE_KEY = "test-boolean-variable"
|
|
9
11
|
|
|
@@ -12,37 +14,34 @@ logger = logging.getLogger(__name__)
|
|
|
12
14
|
|
|
13
15
|
def main():
|
|
14
16
|
"""
|
|
15
|
-
Sample
|
|
17
|
+
Sample usage of the Python Server SDK using Cloud Bucketing.
|
|
16
18
|
For a Django specific sample app, please see https://github.com/DevCycleHQ/python-django-example-app/
|
|
17
|
-
|
|
18
19
|
"""
|
|
19
20
|
logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
|
|
20
21
|
|
|
22
|
+
# create an instance of the DevCycle Client object
|
|
23
|
+
server_sdk_key = os.environ["DEVCYCLE_SERVER_SDK_KEY"]
|
|
21
24
|
options = DevCycleCloudOptions(enable_edge_db=True)
|
|
25
|
+
client = DevCycleCloudClient(server_sdk_key, options)
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
server_sdk_key = os.environ["DVC_SERVER_SDK_KEY"]
|
|
25
|
-
dvc = DevCycleCloudClient(server_sdk_key, options)
|
|
26
|
-
|
|
27
|
-
user = User(user_id="test", email="yo@yo.ca", country="CA")
|
|
28
|
-
event = Event(type="customEvent", target="somevariable.key")
|
|
27
|
+
user = DevCycleUser(user_id="test-1234", email="test-user@domain.com", country="US")
|
|
29
28
|
|
|
30
29
|
# Use variable_value to access the value of a variable directly
|
|
31
|
-
if
|
|
30
|
+
if client.variable_value(user, VARIABLE_KEY, False):
|
|
32
31
|
logger.info(f"Variable {VARIABLE_KEY} is enabled")
|
|
33
32
|
else:
|
|
34
33
|
logger.info(f"Variable {VARIABLE_KEY} is not enabled")
|
|
35
34
|
|
|
36
35
|
# DevCycle handles missing or wrongly typed variables by returning the default value
|
|
37
36
|
# You can check this explicitly by using the variable method
|
|
38
|
-
variable =
|
|
37
|
+
variable = client.variable(user, VARIABLE_KEY + "-does-not-exist", False)
|
|
39
38
|
if variable.isDefaulted:
|
|
40
39
|
logger.info(f"Variable {variable.key} is defaulted to {variable.value}")
|
|
41
40
|
|
|
42
41
|
try:
|
|
43
42
|
# Get all variables by key for user data
|
|
44
|
-
all_variables_response =
|
|
45
|
-
logger.info("All variables:\n
|
|
43
|
+
all_variables_response = client.all_variables(user)
|
|
44
|
+
logger.info(f"All variables:\n{all_variables_response}")
|
|
46
45
|
|
|
47
46
|
if VARIABLE_KEY not in all_variables_response:
|
|
48
47
|
logger.warning(
|
|
@@ -50,14 +49,19 @@ def main():
|
|
|
50
49
|
)
|
|
51
50
|
|
|
52
51
|
# Get all features by key for user data
|
|
53
|
-
all_features_response =
|
|
54
|
-
logger.info("All features:\n
|
|
52
|
+
all_features_response = client.all_features(user)
|
|
53
|
+
logger.info(f"All features:\n{all_features_response}")
|
|
54
|
+
|
|
55
|
+
# Post a custom event to DevCycle for user
|
|
56
|
+
event = DevCycleEvent(
|
|
57
|
+
type=EventType.CustomEvent,
|
|
58
|
+
target="some.variable.key",
|
|
59
|
+
date=datetime.datetime.now(),
|
|
60
|
+
)
|
|
61
|
+
client.track(user, event)
|
|
55
62
|
|
|
56
|
-
# Post events to DevCycle for user
|
|
57
|
-
track_response = dvc.track(user, event)
|
|
58
|
-
logger.info(track_response)
|
|
59
63
|
except Exception as e:
|
|
60
|
-
logger.exception("Exception when calling Devcycle API:
|
|
64
|
+
logger.exception(f"Exception when calling Devcycle API: {e}\n")
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import datetime
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
|
|
7
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
8
|
+
from devcycle_python_sdk.models.event import DevCycleEvent, EventType
|
|
9
|
+
|
|
10
|
+
VARIABLE_KEY = "test-boolean-variable"
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""
|
|
17
|
+
Sample usage of the Python Server SDK using Local Bucketing.
|
|
18
|
+
For a Django specific sample app, please see https://github.com/DevCycleHQ/python-django-example-app/
|
|
19
|
+
"""
|
|
20
|
+
logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
|
|
21
|
+
|
|
22
|
+
# create an instance of the DevCycle Client object
|
|
23
|
+
server_sdk_key = os.environ["DEVCYCLE_SERVER_SDK_KEY"]
|
|
24
|
+
options = DevCycleLocalOptions()
|
|
25
|
+
client = DevCycleLocalClient(server_sdk_key, options)
|
|
26
|
+
|
|
27
|
+
# Wait for DevCycle to initialize and load the configuration
|
|
28
|
+
while not client.is_initialized():
|
|
29
|
+
logger.info("Waiting for DevCycle to initialize...")
|
|
30
|
+
time.sleep(1)
|
|
31
|
+
|
|
32
|
+
user = DevCycleUser(user_id="test-1234", email="test-user@domain.com", country="US")
|
|
33
|
+
|
|
34
|
+
# Use variable_value to access the value of a variable directly
|
|
35
|
+
if client.variable_value(user, VARIABLE_KEY, False):
|
|
36
|
+
logger.info(f"Variable {VARIABLE_KEY} is enabled")
|
|
37
|
+
else:
|
|
38
|
+
logger.info(f"Variable {VARIABLE_KEY} is not enabled")
|
|
39
|
+
|
|
40
|
+
# DevCycle handles missing or wrongly typed variables by returning the default value
|
|
41
|
+
# You can check this explicitly by using the variable method
|
|
42
|
+
variable = client.variable(user, VARIABLE_KEY + "-does-not-exist", False)
|
|
43
|
+
if variable.isDefaulted:
|
|
44
|
+
logger.info(f"Variable {variable.key} is defaulted to {variable.value}")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Get all variables by key for user data
|
|
48
|
+
all_variables_response = client.all_variables(user)
|
|
49
|
+
logger.info(f"All variables:\n{all_variables_response}")
|
|
50
|
+
|
|
51
|
+
if VARIABLE_KEY not in all_variables_response:
|
|
52
|
+
logger.warning(
|
|
53
|
+
f"Variable {VARIABLE_KEY} does not exist - create it in the dashboard for this example"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Get all features by key for user data
|
|
57
|
+
all_features_response = client.all_features(user)
|
|
58
|
+
logger.info(f"All features:\n{all_features_response}")
|
|
59
|
+
|
|
60
|
+
# Post a custom event to DevCycle for user
|
|
61
|
+
event = DevCycleEvent(
|
|
62
|
+
type=EventType.CustomEvent,
|
|
63
|
+
target="some.variable.key",
|
|
64
|
+
date=datetime.datetime.now(),
|
|
65
|
+
)
|
|
66
|
+
client.track(user, event)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.exception(f"Exception when calling DevCycle API: {e}")
|
|
69
|
+
finally:
|
|
70
|
+
client.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
import requests
|
|
3
4
|
import responses
|
|
@@ -11,10 +12,10 @@ from devcycle_python_sdk.exceptions import (
|
|
|
11
12
|
CloudClientUnauthorizedError,
|
|
12
13
|
NotFoundError,
|
|
13
14
|
)
|
|
14
|
-
from devcycle_python_sdk.
|
|
15
|
-
from devcycle_python_sdk.models.event import
|
|
15
|
+
from devcycle_python_sdk.options import DevCycleCloudOptions
|
|
16
|
+
from devcycle_python_sdk.models.event import DevCycleEvent
|
|
16
17
|
from devcycle_python_sdk.models.feature import Feature
|
|
17
|
-
from devcycle_python_sdk.models.user import
|
|
18
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
18
19
|
from devcycle_python_sdk.models.variable import Variable
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
@@ -25,7 +26,7 @@ class BucketingClientTest(unittest.TestCase):
|
|
|
25
26
|
sdk_key = "dvc_server_" + str(uuid.uuid4())
|
|
26
27
|
options = DevCycleCloudOptions(retry_delay=0)
|
|
27
28
|
self.test_client = BucketingAPIClient(sdk_key, options)
|
|
28
|
-
self.test_user =
|
|
29
|
+
self.test_user = DevCycleUser(user_id="test_user_id")
|
|
29
30
|
|
|
30
31
|
@responses.activate
|
|
31
32
|
def test_variable(self):
|
|
@@ -216,12 +217,16 @@ class BucketingClientTest(unittest.TestCase):
|
|
|
216
217
|
result = self.test_client.track(
|
|
217
218
|
self.test_user,
|
|
218
219
|
events=[
|
|
219
|
-
|
|
220
|
+
DevCycleEvent(
|
|
220
221
|
type="sample-event",
|
|
221
222
|
)
|
|
222
223
|
],
|
|
223
224
|
)
|
|
224
225
|
self.assertEqual(result, "success")
|
|
226
|
+
data = json.loads(responses.calls[0].request.body)
|
|
227
|
+
self.assertTrue(type(data["events"][0]["date"]) == int)
|
|
228
|
+
self.assertEqual(len(data["events"]), 1)
|
|
229
|
+
self.assertEqual(data["events"][0]["type"], "sample-event")
|
|
225
230
|
|
|
226
231
|
|
|
227
232
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import unittest
|
|
3
|
+
import uuid
|
|
4
|
+
from os.path import join
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import responses
|
|
9
|
+
from responses.registries import OrderedRegistry
|
|
10
|
+
|
|
11
|
+
from devcycle_python_sdk.api.config_client import ConfigAPIClient
|
|
12
|
+
from devcycle_python_sdk.options import DevCycleLocalOptions
|
|
13
|
+
from devcycle_python_sdk.exceptions import (
|
|
14
|
+
APIClientError,
|
|
15
|
+
APIClientUnauthorizedError,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from test.fixture.data import small_config_json
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConfigAPIClientTest(unittest.TestCase):
|
|
24
|
+
def setUp(self) -> None:
|
|
25
|
+
self.sdk_key = "dvc_server_" + str(uuid.uuid4())
|
|
26
|
+
self.config_url = (
|
|
27
|
+
join(
|
|
28
|
+
"https://config-cdn.devcycle.com/",
|
|
29
|
+
"config",
|
|
30
|
+
"v1",
|
|
31
|
+
"server",
|
|
32
|
+
self.sdk_key,
|
|
33
|
+
)
|
|
34
|
+
+ ".json"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
options = DevCycleLocalOptions(config_retry_delay_ms=0)
|
|
38
|
+
self.test_client = ConfigAPIClient(self.sdk_key, options)
|
|
39
|
+
self.test_etag = str(uuid.uuid4())
|
|
40
|
+
self.test_config_json: dict = small_config_json()
|
|
41
|
+
|
|
42
|
+
@responses.activate
|
|
43
|
+
def test_get_config(self):
|
|
44
|
+
new_etag = str(uuid.uuid4())
|
|
45
|
+
responses.add(
|
|
46
|
+
responses.GET,
|
|
47
|
+
self.config_url,
|
|
48
|
+
headers={"ETag": new_etag},
|
|
49
|
+
json=self.test_config_json,
|
|
50
|
+
)
|
|
51
|
+
result, etag = self.test_client.get_config(config_etag=self.test_etag)
|
|
52
|
+
self.assertDictEqual(result, self.test_config_json)
|
|
53
|
+
self.assertEqual(etag, new_etag)
|
|
54
|
+
|
|
55
|
+
@responses.activate(registry=OrderedRegistry)
|
|
56
|
+
def test_get_config_retries(self):
|
|
57
|
+
responses.add(
|
|
58
|
+
responses.GET,
|
|
59
|
+
self.config_url,
|
|
60
|
+
status=500,
|
|
61
|
+
)
|
|
62
|
+
responses.add(
|
|
63
|
+
responses.GET,
|
|
64
|
+
self.config_url,
|
|
65
|
+
headers={"ETag": self.test_etag},
|
|
66
|
+
json=self.test_config_json,
|
|
67
|
+
)
|
|
68
|
+
result, etag = self.test_client.get_config(config_etag=self.test_etag)
|
|
69
|
+
|
|
70
|
+
self.assertDictEqual(result, self.test_config_json)
|
|
71
|
+
self.assertEqual(etag, self.test_etag)
|
|
72
|
+
|
|
73
|
+
@responses.activate(registry=OrderedRegistry)
|
|
74
|
+
def test_get_config_retries_network_error(self):
|
|
75
|
+
responses.add(
|
|
76
|
+
responses.GET,
|
|
77
|
+
self.config_url,
|
|
78
|
+
body=requests.exceptions.ConnectionError("Network Error"),
|
|
79
|
+
)
|
|
80
|
+
responses.add(
|
|
81
|
+
responses.GET,
|
|
82
|
+
self.config_url,
|
|
83
|
+
headers={"ETag": self.test_etag},
|
|
84
|
+
json=self.test_config_json,
|
|
85
|
+
)
|
|
86
|
+
result, etag = self.test_client.get_config(config_etag=self.test_etag)
|
|
87
|
+
self.assertDictEqual(result, self.test_config_json)
|
|
88
|
+
self.assertEqual(etag, self.test_etag)
|
|
89
|
+
|
|
90
|
+
@responses.activate(registry=OrderedRegistry)
|
|
91
|
+
def test_get_config_retries_exceeded(self):
|
|
92
|
+
for i in range(2):
|
|
93
|
+
responses.add(
|
|
94
|
+
responses.GET,
|
|
95
|
+
self.config_url,
|
|
96
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
97
|
+
)
|
|
98
|
+
with self.assertRaises(APIClientError):
|
|
99
|
+
self.test_client.get_config(config_etag=self.test_etag)
|
|
100
|
+
|
|
101
|
+
@responses.activate
|
|
102
|
+
def test_get_config_not_found(self):
|
|
103
|
+
for i in range(2):
|
|
104
|
+
responses.add(
|
|
105
|
+
responses.GET,
|
|
106
|
+
self.config_url,
|
|
107
|
+
status=HTTPStatus.NOT_FOUND,
|
|
108
|
+
)
|
|
109
|
+
with self.assertRaises(NotFoundError):
|
|
110
|
+
self.test_client.get_config(config_etag=self.test_etag)
|
|
111
|
+
|
|
112
|
+
@responses.activate
|
|
113
|
+
def test_get_config_unauthorized(self):
|
|
114
|
+
for i in range(2):
|
|
115
|
+
responses.add(
|
|
116
|
+
responses.GET,
|
|
117
|
+
self.config_url,
|
|
118
|
+
status=HTTPStatus.UNAUTHORIZED,
|
|
119
|
+
)
|
|
120
|
+
with self.assertRaises(APIClientUnauthorizedError):
|
|
121
|
+
self.test_client.get_config(config_etag=self.test_etag)
|
|
122
|
+
|
|
123
|
+
@responses.activate
|
|
124
|
+
def test_get_config_not_modified(self):
|
|
125
|
+
responses.add(
|
|
126
|
+
responses.GET,
|
|
127
|
+
self.config_url,
|
|
128
|
+
status=HTTPStatus.NOT_MODIFIED,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
new_config, new_etag = self.test_client.get_config(config_etag=self.test_etag)
|
|
132
|
+
self.assertIsNone(new_config)
|
|
133
|
+
self.assertEqual(new_etag, self.test_etag)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import unittest
|
|
3
|
+
import uuid
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
|
|
6
|
+
import responses
|
|
7
|
+
from responses.registries import OrderedRegistry
|
|
8
|
+
|
|
9
|
+
from devcycle_python_sdk.api.event_client import EventAPIClient
|
|
10
|
+
from devcycle_python_sdk.options import DevCycleLocalOptions
|
|
11
|
+
from devcycle_python_sdk.exceptions import (
|
|
12
|
+
APIClientError,
|
|
13
|
+
APIClientUnauthorizedError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
)
|
|
16
|
+
from devcycle_python_sdk.models.event import (
|
|
17
|
+
UserEventsBatchRecord,
|
|
18
|
+
RequestEvent,
|
|
19
|
+
EventType,
|
|
20
|
+
)
|
|
21
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EventAPIClientTest(unittest.TestCase):
|
|
27
|
+
def setUp(self) -> None:
|
|
28
|
+
self.sdk_key = "dvc_server_" + str(uuid.uuid4())
|
|
29
|
+
options = DevCycleLocalOptions(events_api_uri="http://localhost:8080")
|
|
30
|
+
|
|
31
|
+
self.test_client = EventAPIClient(self.sdk_key, options)
|
|
32
|
+
self.test_batch_url = "http://localhost:8080/v1/events/batch"
|
|
33
|
+
|
|
34
|
+
self.test_user = DevCycleUser(user_id="123")
|
|
35
|
+
events = [
|
|
36
|
+
RequestEvent(
|
|
37
|
+
type=EventType.VariableDefaulted,
|
|
38
|
+
user_id="123",
|
|
39
|
+
date="2023-06-27T12:50:19.871Z",
|
|
40
|
+
clientDate="2023-06-27T12:50:19.871Z",
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
self.test_batch = [UserEventsBatchRecord(user=self.test_user, events=events)]
|
|
44
|
+
|
|
45
|
+
@responses.activate
|
|
46
|
+
def test_publish_events(self):
|
|
47
|
+
responses.add(
|
|
48
|
+
responses.POST,
|
|
49
|
+
self.test_batch_url,
|
|
50
|
+
json={"message": "Successfully received 1 event batches."},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
message = self.test_client.publish_events(self.test_batch)
|
|
54
|
+
self.assertEqual(message, "Successfully received 1 event batches.")
|
|
55
|
+
|
|
56
|
+
@responses.activate
|
|
57
|
+
def test_publish_events_empty_batch(self):
|
|
58
|
+
responses.add(
|
|
59
|
+
responses.POST,
|
|
60
|
+
self.test_batch_url,
|
|
61
|
+
json={"message": "Successfully received 1 event batches."},
|
|
62
|
+
)
|
|
63
|
+
message = self.test_client.publish_events([])
|
|
64
|
+
self.assertEqual(message, "Successfully received 1 event batches.")
|
|
65
|
+
|
|
66
|
+
@responses.activate(registry=OrderedRegistry)
|
|
67
|
+
def test_publish_events_retries_exceeded(self):
|
|
68
|
+
for i in range(2):
|
|
69
|
+
responses.add(
|
|
70
|
+
responses.POST,
|
|
71
|
+
self.test_batch_url,
|
|
72
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
73
|
+
)
|
|
74
|
+
with self.assertRaises(APIClientError):
|
|
75
|
+
self.test_client.publish_events(self.test_batch)
|
|
76
|
+
|
|
77
|
+
@responses.activate
|
|
78
|
+
def test_publish_events_not_found(self):
|
|
79
|
+
for i in range(2):
|
|
80
|
+
responses.add(
|
|
81
|
+
responses.POST,
|
|
82
|
+
self.test_batch_url,
|
|
83
|
+
status=HTTPStatus.NOT_FOUND,
|
|
84
|
+
)
|
|
85
|
+
with self.assertRaises(NotFoundError):
|
|
86
|
+
self.test_client.publish_events(self.test_batch)
|
|
87
|
+
|
|
88
|
+
@responses.activate
|
|
89
|
+
def test_publish_events_unauthorized(self):
|
|
90
|
+
for i in range(2):
|
|
91
|
+
responses.add(
|
|
92
|
+
responses.POST,
|
|
93
|
+
self.test_batch_url,
|
|
94
|
+
status=HTTPStatus.UNAUTHORIZED,
|
|
95
|
+
)
|
|
96
|
+
with self.assertRaises(APIClientUnauthorizedError):
|
|
97
|
+
self.test_client.publish_events(self.test_batch)
|