devcycle-python-server-sdk 3.12.0__py3-none-any.whl → 3.13.1__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/api/local_bucketing.py +19 -4
- devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
- devcycle_python_sdk/cloud_client.py +3 -3
- devcycle_python_sdk/local_client.py +13 -5
- devcycle_python_sdk/managers/config_manager.py +4 -0
- devcycle_python_sdk/managers/eval_hooks_manager.py +15 -4
- devcycle_python_sdk/managers/event_queue_manager.py +0 -1
- devcycle_python_sdk/models/config_metadata.py +40 -0
- devcycle_python_sdk/models/environment_metadata.py +26 -0
- devcycle_python_sdk/models/eval_hook.py +5 -2
- devcycle_python_sdk/models/eval_hook_context.py +23 -5
- devcycle_python_sdk/models/eval_reason.py +0 -1
- devcycle_python_sdk/models/project_metadata.py +24 -0
- devcycle_python_sdk/models/user.py +3 -3
- devcycle_python_sdk/models/variable_metadata.py +22 -0
- devcycle_python_sdk/open_feature_provider/provider.py +7 -5
- devcycle_python_sdk/options.py +1 -1
- devcycle_python_sdk/protobuf/utils.py +9 -3
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +34 -42
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +11 -11
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/METADATA +2 -2
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/RECORD +32 -28
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/licenses/LICENSE +1 -1
- example/openfeature_example.py +175 -14
- test/api/test_local_bucketing.py +6 -3
- test/models/test_user.py +0 -1
- test/openfeature_test/test_provider.py +0 -1
- test/test_cloud_client.py +41 -6
- test/test_local_client.py +53 -4
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/WHEEL +0 -0
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/top_level.txt +0 -0
|
@@ -7,14 +7,14 @@ from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Opti
|
|
|
7
7
|
DESCRIPTOR: _descriptor.FileDescriptor
|
|
8
8
|
|
|
9
9
|
class VariableType_PB(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
10
|
-
__slots__ =
|
|
10
|
+
__slots__ = []
|
|
11
11
|
Boolean: _ClassVar[VariableType_PB]
|
|
12
12
|
Number: _ClassVar[VariableType_PB]
|
|
13
13
|
String: _ClassVar[VariableType_PB]
|
|
14
14
|
JSON: _ClassVar[VariableType_PB]
|
|
15
15
|
|
|
16
16
|
class CustomDataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
17
|
-
__slots__ =
|
|
17
|
+
__slots__ = []
|
|
18
18
|
Bool: _ClassVar[CustomDataType]
|
|
19
19
|
Num: _ClassVar[CustomDataType]
|
|
20
20
|
Str: _ClassVar[CustomDataType]
|
|
@@ -29,7 +29,7 @@ Str: CustomDataType
|
|
|
29
29
|
Null: CustomDataType
|
|
30
30
|
|
|
31
31
|
class NullableString(_message.Message):
|
|
32
|
-
__slots__ =
|
|
32
|
+
__slots__ = ["value", "isNull"]
|
|
33
33
|
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
34
34
|
ISNULL_FIELD_NUMBER: _ClassVar[int]
|
|
35
35
|
value: str
|
|
@@ -37,7 +37,7 @@ class NullableString(_message.Message):
|
|
|
37
37
|
def __init__(self, value: _Optional[str] = ..., isNull: bool = ...) -> None: ...
|
|
38
38
|
|
|
39
39
|
class NullableDouble(_message.Message):
|
|
40
|
-
__slots__ =
|
|
40
|
+
__slots__ = ["value", "isNull"]
|
|
41
41
|
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
42
42
|
ISNULL_FIELD_NUMBER: _ClassVar[int]
|
|
43
43
|
value: float
|
|
@@ -45,7 +45,7 @@ class NullableDouble(_message.Message):
|
|
|
45
45
|
def __init__(self, value: _Optional[float] = ..., isNull: bool = ...) -> None: ...
|
|
46
46
|
|
|
47
47
|
class CustomDataValue(_message.Message):
|
|
48
|
-
__slots__ =
|
|
48
|
+
__slots__ = ["type", "boolValue", "doubleValue", "stringValue"]
|
|
49
49
|
TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
50
50
|
BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
51
51
|
DOUBLEVALUE_FIELD_NUMBER: _ClassVar[int]
|
|
@@ -57,9 +57,9 @@ class CustomDataValue(_message.Message):
|
|
|
57
57
|
def __init__(self, type: _Optional[_Union[CustomDataType, str]] = ..., boolValue: bool = ..., doubleValue: _Optional[float] = ..., stringValue: _Optional[str] = ...) -> None: ...
|
|
58
58
|
|
|
59
59
|
class NullableCustomData(_message.Message):
|
|
60
|
-
__slots__ =
|
|
60
|
+
__slots__ = ["value", "isNull"]
|
|
61
61
|
class ValueEntry(_message.Message):
|
|
62
|
-
__slots__ =
|
|
62
|
+
__slots__ = ["key", "value"]
|
|
63
63
|
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
64
64
|
VALUE_FIELD_NUMBER: _ClassVar[int]
|
|
65
65
|
key: str
|
|
@@ -72,7 +72,7 @@ class NullableCustomData(_message.Message):
|
|
|
72
72
|
def __init__(self, value: _Optional[_Mapping[str, CustomDataValue]] = ..., isNull: bool = ...) -> None: ...
|
|
73
73
|
|
|
74
74
|
class VariableForUserParams_PB(_message.Message):
|
|
75
|
-
__slots__ =
|
|
75
|
+
__slots__ = ["sdkKey", "variableKey", "variableType", "user", "shouldTrackEvent"]
|
|
76
76
|
SDKKEY_FIELD_NUMBER: _ClassVar[int]
|
|
77
77
|
VARIABLEKEY_FIELD_NUMBER: _ClassVar[int]
|
|
78
78
|
VARIABLETYPE_FIELD_NUMBER: _ClassVar[int]
|
|
@@ -86,7 +86,7 @@ class VariableForUserParams_PB(_message.Message):
|
|
|
86
86
|
def __init__(self, sdkKey: _Optional[str] = ..., variableKey: _Optional[str] = ..., variableType: _Optional[_Union[VariableType_PB, str]] = ..., user: _Optional[_Union[DVCUser_PB, _Mapping]] = ..., shouldTrackEvent: bool = ...) -> None: ...
|
|
87
87
|
|
|
88
88
|
class DVCUser_PB(_message.Message):
|
|
89
|
-
__slots__ =
|
|
89
|
+
__slots__ = ["user_id", "email", "name", "language", "country", "appBuild", "appVersion", "deviceModel", "customData", "privateCustomData"]
|
|
90
90
|
USER_ID_FIELD_NUMBER: _ClassVar[int]
|
|
91
91
|
EMAIL_FIELD_NUMBER: _ClassVar[int]
|
|
92
92
|
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
@@ -110,7 +110,7 @@ class DVCUser_PB(_message.Message):
|
|
|
110
110
|
def __init__(self, user_id: _Optional[str] = ..., email: _Optional[_Union[NullableString, _Mapping]] = ..., name: _Optional[_Union[NullableString, _Mapping]] = ..., language: _Optional[_Union[NullableString, _Mapping]] = ..., country: _Optional[_Union[NullableString, _Mapping]] = ..., appBuild: _Optional[_Union[NullableDouble, _Mapping]] = ..., appVersion: _Optional[_Union[NullableString, _Mapping]] = ..., deviceModel: _Optional[_Union[NullableString, _Mapping]] = ..., customData: _Optional[_Union[NullableCustomData, _Mapping]] = ..., privateCustomData: _Optional[_Union[NullableCustomData, _Mapping]] = ...) -> None: ...
|
|
111
111
|
|
|
112
112
|
class SDKVariable_PB(_message.Message):
|
|
113
|
-
__slots__ =
|
|
113
|
+
__slots__ = ["_id", "type", "key", "boolValue", "doubleValue", "stringValue", "evalReason", "_feature", "eval"]
|
|
114
114
|
_ID_FIELD_NUMBER: _ClassVar[int]
|
|
115
115
|
TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
116
116
|
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
@@ -132,7 +132,7 @@ class SDKVariable_PB(_message.Message):
|
|
|
132
132
|
def __init__(self, _id: _Optional[str] = ..., type: _Optional[_Union[VariableType_PB, str]] = ..., key: _Optional[str] = ..., boolValue: bool = ..., doubleValue: _Optional[float] = ..., stringValue: _Optional[str] = ..., evalReason: _Optional[_Union[NullableString, _Mapping]] = ..., _feature: _Optional[_Union[NullableString, _Mapping]] = ..., eval: _Optional[_Union[EvalReason_PB, _Mapping]] = ...) -> None: ...
|
|
133
133
|
|
|
134
134
|
class EvalReason_PB(_message.Message):
|
|
135
|
-
__slots__ =
|
|
135
|
+
__slots__ = ["reason", "details", "target_id"]
|
|
136
136
|
REASON_FIELD_NUMBER: _ClassVar[int]
|
|
137
137
|
DETAILS_FIELD_NUMBER: _ClassVar[int]
|
|
138
138
|
TARGET_ID_FIELD_NUMBER: _ClassVar[int]
|
{devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devcycle-python-server-sdk
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.1
|
|
4
4
|
Summary: DevCycle Python SDK
|
|
5
5
|
Home-page: https://github.com/devcycleHQ/python-server-sdk
|
|
6
6
|
Author-email:
|
|
@@ -12,7 +12,7 @@ Requires-Dist: urllib3>=1.15.1
|
|
|
12
12
|
Requires-Dist: requests>=2.32
|
|
13
13
|
Requires-Dist: wasmtime~=30.0.0
|
|
14
14
|
Requires-Dist: protobuf>=4.23.3
|
|
15
|
-
Requires-Dist: openfeature-sdk
|
|
15
|
+
Requires-Dist: openfeature-sdk~=0.8.0
|
|
16
16
|
Requires-Dist: launchdarkly-eventsource>=1.2.1
|
|
17
17
|
Requires-Dist: responses>=0.23.1
|
|
18
18
|
Dynamic: description
|
{devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/RECORD
RENAMED
|
@@ -1,56 +1,60 @@
|
|
|
1
|
-
devcycle_python_sdk/VERSION.txt,sha256=
|
|
1
|
+
devcycle_python_sdk/VERSION.txt,sha256=d1t-sfDGOHao5o2Fv5VaJziw754bHVxGJ49Qa9H8BMo,7
|
|
2
2
|
devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
|
|
3
|
-
devcycle_python_sdk/bucketing-lib.release.wasm,sha256=
|
|
4
|
-
devcycle_python_sdk/cloud_client.py,sha256=
|
|
3
|
+
devcycle_python_sdk/bucketing-lib.release.wasm,sha256=6iooyHRR-GBfrBH0_mleWyaNHr9uM_nVPS-IXGROGCY,224670
|
|
4
|
+
devcycle_python_sdk/cloud_client.py,sha256=dR2ye29Up699hO1cdrAWeoXIOBPVoSvQ4aKe2gSBRno,9414
|
|
5
5
|
devcycle_python_sdk/devcycle_client.py,sha256=OqNza7OXOSRmMQq2kmyivljmH1GyN4dTiz0EhvDvt8w,1069
|
|
6
6
|
devcycle_python_sdk/exceptions.py,sha256=Et7fS8pEFbHkFY86T2pJoEUstwjh4_rzhJxVH8fzAns,1053
|
|
7
|
-
devcycle_python_sdk/local_client.py,sha256=
|
|
8
|
-
devcycle_python_sdk/options.py,sha256=
|
|
7
|
+
devcycle_python_sdk/local_client.py,sha256=HbHIGLeIw9fHOSwp7RJ8ozIO_IqQ23bzSoGoGFLp0Kk,12083
|
|
8
|
+
devcycle_python_sdk/options.py,sha256=DRvr1IQRW3BDHl81TvORfCtt0qVW42c8PoYjMMPkzwQ,5252
|
|
9
9
|
devcycle_python_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
devcycle_python_sdk/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
11
11
|
devcycle_python_sdk/api/backoff.py,sha256=F6WYCloaZD9CjNu6mKBMq7K20S8-i2WVU6X05ZRoawE,303
|
|
12
12
|
devcycle_python_sdk/api/bucketing_client.py,sha256=BRQWtTnIyLdWoFT7sPHPGg8kstDtW-jx1N4og3_SEgI,5565
|
|
13
13
|
devcycle_python_sdk/api/config_client.py,sha256=73aQAq130rTHpQrVDX6n_DwFKz0WuKxRoLDiE3vSoAM,5077
|
|
14
14
|
devcycle_python_sdk/api/event_client.py,sha256=WxkuV6geEC9MgG4FN36HeEQQa22ilq2mkOVMZkpiEk4,3356
|
|
15
|
-
devcycle_python_sdk/api/local_bucketing.py,sha256=
|
|
15
|
+
devcycle_python_sdk/api/local_bucketing.py,sha256=kXggXhv2PDQxDoLseuJwWbge8EgeqL0FWTmEIn8TUQc,16704
|
|
16
16
|
devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
devcycle_python_sdk/managers/config_manager.py,sha256=
|
|
18
|
-
devcycle_python_sdk/managers/eval_hooks_manager.py,sha256=
|
|
19
|
-
devcycle_python_sdk/managers/event_queue_manager.py,sha256=
|
|
17
|
+
devcycle_python_sdk/managers/config_manager.py,sha256=QsJPMnf0wgNND3Yqhv125ckta_YFOMUb8I8pFq64tOE,5827
|
|
18
|
+
devcycle_python_sdk/managers/eval_hooks_manager.py,sha256=YOLj0IvNFqDbWvPA2aOqT15yKkwsdnZT9MQ7MHTv6M4,2963
|
|
19
|
+
devcycle_python_sdk/managers/event_queue_manager.py,sha256=8tqPZDD0-AWmz_NcWJBb80OIMzNBpDgbafpFefiOOmk,8109
|
|
20
20
|
devcycle_python_sdk/managers/sse_manager.py,sha256=hSGpnZ6gNbQXGotqNxjogoB82znAyMMFhqdlqeCc5bI,2586
|
|
21
21
|
devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
devcycle_python_sdk/models/bucketed_config.py,sha256=qTfhIViX7m2WS2oDBBCmKKzPsJG6DEav3zCEXwP27nA,3980
|
|
23
|
+
devcycle_python_sdk/models/config_metadata.py,sha256=HHhw2ncHOUAC3SH-IokVP4GrNLbflcGE9p7a2ZSuksA,1413
|
|
24
|
+
devcycle_python_sdk/models/environment_metadata.py,sha256=Zal9Ly15GaMcUBcD7CujNvtl5w_qfL8TQtLSRBpTHMo,629
|
|
23
25
|
devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
|
|
24
|
-
devcycle_python_sdk/models/eval_hook.py,sha256=
|
|
25
|
-
devcycle_python_sdk/models/eval_hook_context.py,sha256=
|
|
26
|
-
devcycle_python_sdk/models/eval_reason.py,sha256=
|
|
26
|
+
devcycle_python_sdk/models/eval_hook.py,sha256=k9w-AafvR-X7i8uf5nVuNEbD_PRS8QQmxW_90KVgGHA,742
|
|
27
|
+
devcycle_python_sdk/models/eval_hook_context.py,sha256=FYfMfXgtlW1O70khIcWF87yie2SWW4iWH2ehPex9paM,928
|
|
28
|
+
devcycle_python_sdk/models/eval_reason.py,sha256=obBjIWt8ZUh6knpVGsmCfWOCj4c7DXdH7K-FY-pchsU,901
|
|
27
29
|
devcycle_python_sdk/models/event.py,sha256=Sj3eEopuPp8QwQl_Oz8E6Y9Ph6nLG0HxU7eR7xSbA7g,3722
|
|
28
30
|
devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
|
|
29
31
|
devcycle_python_sdk/models/platform_data.py,sha256=nyQsgI6pcTd1FL3Y2ACmpvTYb08R_UtWul25OWZstps,849
|
|
30
|
-
devcycle_python_sdk/models/
|
|
32
|
+
devcycle_python_sdk/models/project_metadata.py,sha256=zRoE_oy_N3isqfrA5hAZ7S-7i5QBTSu9OGigFAvTqi8,602
|
|
33
|
+
devcycle_python_sdk/models/user.py,sha256=DgSP4iq9IxEOOBmYIKuCLXi6lVqYkt01_Wm0Z4-QWBo,7055
|
|
31
34
|
devcycle_python_sdk/models/variable.py,sha256=NXubhYWXYRAUEh4kDe2Tu9T5QnZmQ3roYVPbP-u2H3s,3030
|
|
35
|
+
devcycle_python_sdk/models/variable_metadata.py,sha256=oeH5fxDFLgAyc_B1RuOYRp4w68lTaBxTPvNgnXz7alE,583
|
|
32
36
|
devcycle_python_sdk/open_feature_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
-
devcycle_python_sdk/open_feature_provider/provider.py,sha256=
|
|
37
|
+
devcycle_python_sdk/open_feature_provider/provider.py,sha256=Iby2X2YuYgR6-W00NOWE4K7YdSUHODeqjm4Eog7pACc,5693
|
|
34
38
|
devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
devcycle_python_sdk/protobuf/utils.py,sha256=
|
|
36
|
-
devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=
|
|
37
|
-
devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=
|
|
39
|
+
devcycle_python_sdk/protobuf/utils.py,sha256=SwsXH5QGAfbSn1FvgwO3x4W8uTYLsL1r3E-P5nDD4Nw,5528
|
|
40
|
+
devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=SIFCo4AbjtlWQh2DYxkrWCR85PQajA4PIofXoRe4Gds,4956
|
|
41
|
+
devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=OU4ORhjnbXy0AqdkxyARxg4JXGTPDuDzBW2W9RGdcMs,6623
|
|
38
42
|
devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
39
43
|
devcycle_python_sdk/util/strings.py,sha256=gH0Xm3gIXNqn6hczKPk10Jn5_9knuD7OB5MrB7bqztw,252
|
|
40
44
|
devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
|
|
41
|
-
devcycle_python_server_sdk-3.
|
|
45
|
+
devcycle_python_server_sdk-3.13.1.dist-info/licenses/LICENSE,sha256=RlvZsEX5o__wLAA-VE7mG5hzTPjnFzPJKNdXXKvZ854,1071
|
|
42
46
|
example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
47
|
example/cloud_client_example.py,sha256=YD8ZsTv3dPYfbFUKraRpRT0VsJGrPwjm8ZCuA5H54yI,2463
|
|
44
48
|
example/local_bucketing_client_example.py,sha256=TaIfy3VnW9fXii9NPYs5UOuP4fLKruXMjnZApveqzl4,2796
|
|
45
|
-
example/openfeature_example.py,sha256=
|
|
49
|
+
example/openfeature_example.py,sha256=8rdEtG_RiEbCwbQQWcCXtfZURz2Z393WoczyperkNug,8105
|
|
46
50
|
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
-
test/test_cloud_client.py,sha256=
|
|
48
|
-
test/test_local_client.py,sha256=
|
|
51
|
+
test/test_cloud_client.py,sha256=CoK4xfHPMGyJ4hXrdI98e5f4zEL51vx31u_xmC3FEtc,15957
|
|
52
|
+
test/test_local_client.py,sha256=yg19ddGmg9t1TfjT-4EgHp0cjfxHjfJZOGIFM8mSNX8,18078
|
|
49
53
|
test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
54
|
test/api/test_bucketing_client.py,sha256=M2mjACowLvbsIjXdP-w8-fwY02xqv1X1r_h9v-pXa7A,7739
|
|
51
55
|
test/api/test_config_client.py,sha256=3BwMm4y-4NQcdW2DDGppoJLIF-ffgjlUJti2Qjw1D1M,5129
|
|
52
56
|
test/api/test_event_client.py,sha256=v4Xoum-PdywfczrfMV1llM5vrIJoqHybzxPLVokh09g,3310
|
|
53
|
-
test/api/test_local_bucketing.py,sha256=
|
|
57
|
+
test/api/test_local_bucketing.py,sha256=ECouaWpgWrlfEBtTK5OavmYX87XiXKg-9epmzTOCMdE,16126
|
|
54
58
|
test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
59
|
test/fixture/data.py,sha256=6ReLxWPMhCDbJ5Ex24_iR0PJNjKzPFV1I9j_v0FVbBU,1462
|
|
56
60
|
test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -58,15 +62,15 @@ test/managers/test_config_manager.py,sha256=2TUnt8501KM7UlJNH7srIhvC-izehD7nOfqZ
|
|
|
58
62
|
test/managers/test_event_queue_manager.py,sha256=z850atk8zAlD7sYaA1OJwgCFcfC0ps4NEk4XUnNCq1U,11629
|
|
59
63
|
test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
64
|
test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
|
|
61
|
-
test/models/test_user.py,sha256=
|
|
65
|
+
test/models/test_user.py,sha256=nuGJvR2KHilRlx5DDEzh1F-lpZ7qT7H88kqPvfcFfM8,10539
|
|
62
66
|
test/openfeature_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
|
-
test/openfeature_test/test_provider.py,sha256=
|
|
67
|
+
test/openfeature_test/test_provider.py,sha256=vj8eKprkF-psRdy54AtitYoEqBJKhDpfEzEDdhM-3FE,10931
|
|
64
68
|
test/openfeature_test/test_provider_local_sdk.py,sha256=ExkA1LYLuvW-dD4Bw2BKggzDQ8GpnIp3WyNK4rUZgJQ,5696
|
|
65
69
|
test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
70
|
test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
|
|
67
71
|
test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
|
|
68
72
|
test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
|
|
69
|
-
devcycle_python_server_sdk-3.
|
|
70
|
-
devcycle_python_server_sdk-3.
|
|
71
|
-
devcycle_python_server_sdk-3.
|
|
72
|
-
devcycle_python_server_sdk-3.
|
|
73
|
+
devcycle_python_server_sdk-3.13.1.dist-info/METADATA,sha256=1mU8MQraVdQxnwAxNqGvxsQ6GmpBxsVOEk9O_MpAwqA,723
|
|
74
|
+
devcycle_python_server_sdk-3.13.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
devcycle_python_server_sdk-3.13.1.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
|
|
76
|
+
devcycle_python_server_sdk-3.13.1.dist-info/RECORD,,
|
example/openfeature_example.py
CHANGED
|
@@ -7,19 +7,32 @@ from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
|
|
|
7
7
|
from openfeature import api
|
|
8
8
|
from openfeature.evaluation_context import EvaluationContext
|
|
9
9
|
|
|
10
|
-
FLAG_KEY = "test-boolean-variable"
|
|
11
|
-
|
|
12
10
|
logger = logging.getLogger(__name__)
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
def main():
|
|
16
14
|
"""
|
|
17
15
|
Sample usage of the DevCycle OpenFeature Provider along with the Python Server SDK using Local Bucketing.
|
|
16
|
+
|
|
17
|
+
This example demonstrates how to use all variable types supported by DevCycle through OpenFeature:
|
|
18
|
+
- Boolean variables
|
|
19
|
+
- String variables
|
|
20
|
+
- Number variables (integer and float)
|
|
21
|
+
- JSON object variables
|
|
22
|
+
|
|
23
|
+
See DEVCYCLE_SETUP.md for instructions on creating the required variables in DevCycle.
|
|
18
24
|
"""
|
|
19
25
|
logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
|
|
20
26
|
|
|
21
27
|
# create an instance of the DevCycle Client object
|
|
22
|
-
server_sdk_key = os.environ
|
|
28
|
+
server_sdk_key = os.environ.get("DEVCYCLE_SERVER_SDK_KEY")
|
|
29
|
+
if not server_sdk_key:
|
|
30
|
+
logger.error("DEVCYCLE_SERVER_SDK_KEY environment variable is not set")
|
|
31
|
+
logger.error(
|
|
32
|
+
"Please set it with: export DEVCYCLE_SERVER_SDK_KEY='your-sdk-key'"
|
|
33
|
+
)
|
|
34
|
+
exit(1)
|
|
35
|
+
|
|
23
36
|
devcycle_client = DevCycleLocalClient(server_sdk_key, DevCycleLocalOptions())
|
|
24
37
|
|
|
25
38
|
# Wait for DevCycle to initialize and load the configuration
|
|
@@ -32,6 +45,8 @@ def main():
|
|
|
32
45
|
logger.error("DevCycle failed to initialize")
|
|
33
46
|
exit(1)
|
|
34
47
|
|
|
48
|
+
logger.info("DevCycle initialized successfully!\n")
|
|
49
|
+
|
|
35
50
|
# set the provider for OpenFeature
|
|
36
51
|
api.set_provider(devcycle_client.get_openfeature_provider())
|
|
37
52
|
|
|
@@ -53,22 +68,168 @@ def main():
|
|
|
53
68
|
},
|
|
54
69
|
)
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
logger.info("=" * 60)
|
|
72
|
+
logger.info("Testing Boolean Variable")
|
|
73
|
+
logger.info("=" * 60)
|
|
74
|
+
|
|
75
|
+
# Test Boolean Variable
|
|
76
|
+
boolean_details = open_feature_client.get_boolean_details(
|
|
77
|
+
"test-boolean-variable", False, context
|
|
78
|
+
)
|
|
79
|
+
logger.info("Variable Key: test-boolean-variable")
|
|
80
|
+
logger.info("Value: {boolean_details.value}")
|
|
81
|
+
logger.info("Reason: {boolean_details.reason}")
|
|
82
|
+
if boolean_details.value:
|
|
83
|
+
logger.info("✓ Boolean variable is ENABLED")
|
|
59
84
|
else:
|
|
60
|
-
logger.info(
|
|
85
|
+
logger.info("✗ Boolean variable is DISABLED")
|
|
86
|
+
|
|
87
|
+
logger.info("\n" + "=" * 60)
|
|
88
|
+
logger.info("Testing String Variable")
|
|
89
|
+
logger.info("=" * 60)
|
|
61
90
|
|
|
62
|
-
#
|
|
63
|
-
|
|
91
|
+
# Test String Variable
|
|
92
|
+
open_feature_client.get_string_details(
|
|
93
|
+
"test-string-variable", "default string", context
|
|
94
|
+
)
|
|
95
|
+
logger.info("Variable Key: test-string-variable")
|
|
96
|
+
logger.info("Value: {string_details.value}")
|
|
97
|
+
logger.info("Reason: {string_details.reason}")
|
|
98
|
+
|
|
99
|
+
logger.info("\n" + "=" * 60)
|
|
100
|
+
logger.info("Testing Number Variable (Integer)")
|
|
101
|
+
logger.info("=" * 60)
|
|
102
|
+
|
|
103
|
+
# Test Number Variable (Integer)
|
|
104
|
+
open_feature_client.get_integer_details("test-number-variable", 0, context)
|
|
105
|
+
logger.info("Variable Key: test-number-variable")
|
|
106
|
+
logger.info("Value: {integer_details.value}")
|
|
107
|
+
logger.info("Reason: {integer_details.reason}")
|
|
108
|
+
|
|
109
|
+
logger.info("\n" + "=" * 60)
|
|
110
|
+
logger.info("Testing Number Variable (Float)")
|
|
111
|
+
logger.info("=" * 60)
|
|
112
|
+
|
|
113
|
+
# Test Number Variable as Float
|
|
114
|
+
# Note: If the DevCycle variable is an integer, it will be cast to float
|
|
115
|
+
open_feature_client.get_float_value("test-number-variable", 0.0, context)
|
|
116
|
+
logger.info("Variable Key: test-number-variable (as float)")
|
|
117
|
+
logger.info("Value: {float_value}")
|
|
118
|
+
|
|
119
|
+
logger.info("\n" + "=" * 60)
|
|
120
|
+
logger.info("Testing JSON Object Variable")
|
|
121
|
+
logger.info("=" * 60)
|
|
122
|
+
|
|
123
|
+
# Test JSON Object Variable
|
|
124
|
+
open_feature_client.get_object_details(
|
|
64
125
|
"test-json-variable", {"default": "value"}, context
|
|
65
126
|
)
|
|
66
|
-
logger.info(
|
|
127
|
+
logger.info("Variable Key: test-json-variable")
|
|
128
|
+
logger.info("Value: {json_details.value}")
|
|
129
|
+
logger.info("Reason: {json_details.reason}")
|
|
130
|
+
|
|
131
|
+
logger.info("\n" + "=" * 60)
|
|
132
|
+
logger.info("Testing Object Variable - Empty Dictionary")
|
|
133
|
+
logger.info("=" * 60)
|
|
134
|
+
|
|
135
|
+
# Test with empty dictionary default (valid per OpenFeature spec)
|
|
136
|
+
open_feature_client.get_object_value("test-json-variable", {}, context)
|
|
137
|
+
logger.info("Variable Key: test-json-variable (with empty default)")
|
|
138
|
+
logger.info("Value: {empty_dict_result}")
|
|
139
|
+
|
|
140
|
+
logger.info("\n" + "=" * 60)
|
|
141
|
+
logger.info("Testing Object Variable - Mixed Types")
|
|
142
|
+
logger.info("=" * 60)
|
|
143
|
+
|
|
144
|
+
# Test with flat dictionary containing mixed primitive types
|
|
145
|
+
# OpenFeature allows string, int, float, bool, and None in flat dictionaries
|
|
146
|
+
mixed_default = {
|
|
147
|
+
"string_key": "hello",
|
|
148
|
+
"int_key": 42,
|
|
149
|
+
"float_key": 3.14,
|
|
150
|
+
"bool_key": True,
|
|
151
|
+
"none_key": None,
|
|
152
|
+
}
|
|
153
|
+
mixed_result = open_feature_client.get_object_value(
|
|
154
|
+
"test-json-variable", mixed_default, context
|
|
155
|
+
)
|
|
156
|
+
logger.info("Variable Key: test-json-variable (with mixed types)")
|
|
157
|
+
logger.info("Value: {mixed_result}")
|
|
158
|
+
logger.info(
|
|
159
|
+
f"Value types: {[(k, type(v).__name__) for k, v in mixed_result.items()]}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
logger.info("\n" + "=" * 60)
|
|
163
|
+
logger.info("Testing Object Variable - All String Values")
|
|
164
|
+
logger.info("=" * 60)
|
|
165
|
+
|
|
166
|
+
# Test with all string values
|
|
167
|
+
string_dict_default = {
|
|
168
|
+
"name": "John Doe",
|
|
169
|
+
"email": "john@example.com",
|
|
170
|
+
"status": "active",
|
|
171
|
+
}
|
|
172
|
+
open_feature_client.get_object_value(
|
|
173
|
+
"test-json-variable", string_dict_default, context
|
|
174
|
+
)
|
|
175
|
+
logger.info("Variable Key: test-json-variable (all strings)")
|
|
176
|
+
logger.info("Value: {string_dict_result}")
|
|
177
|
+
|
|
178
|
+
logger.info("\n" + "=" * 60)
|
|
179
|
+
logger.info("Testing Object Variable - Numeric Values")
|
|
180
|
+
logger.info("=" * 60)
|
|
181
|
+
|
|
182
|
+
# Test with numeric values (integers and floats)
|
|
183
|
+
numeric_dict_default = {"count": 100, "percentage": 85.5, "threshold": 0}
|
|
184
|
+
open_feature_client.get_object_value(
|
|
185
|
+
"test-json-variable", numeric_dict_default, context
|
|
186
|
+
)
|
|
187
|
+
logger.info("Variable Key: test-json-variable (numeric)")
|
|
188
|
+
logger.info("Value: {numeric_dict_result}")
|
|
189
|
+
|
|
190
|
+
logger.info("\n" + "=" * 60)
|
|
191
|
+
logger.info("Testing Object Variable - Boolean Flags")
|
|
192
|
+
logger.info("=" * 60)
|
|
193
|
+
|
|
194
|
+
# Test with boolean values
|
|
195
|
+
bool_dict_default = {"feature_a": True, "feature_b": False, "feature_c": True}
|
|
196
|
+
open_feature_client.get_object_value(
|
|
197
|
+
"test-json-variable", bool_dict_default, context
|
|
198
|
+
)
|
|
199
|
+
logger.info("Variable Key: test-json-variable (booleans)")
|
|
200
|
+
logger.info("Value: {bool_dict_result}")
|
|
201
|
+
|
|
202
|
+
logger.info("\n" + "=" * 60)
|
|
203
|
+
logger.info("Testing Object Variable - With None Values")
|
|
204
|
+
logger.info("=" * 60)
|
|
205
|
+
|
|
206
|
+
# Test with None values (valid per OpenFeature spec for flat dictionaries)
|
|
207
|
+
none_dict_default = {
|
|
208
|
+
"optional_field": None,
|
|
209
|
+
"required_field": "value",
|
|
210
|
+
"nullable_count": None,
|
|
211
|
+
}
|
|
212
|
+
open_feature_client.get_object_value(
|
|
213
|
+
"test-json-variable", none_dict_default, context
|
|
214
|
+
)
|
|
215
|
+
logger.info("Variable Key: test-json-variable (with None)")
|
|
216
|
+
logger.info("Value: {none_dict_result}")
|
|
217
|
+
|
|
218
|
+
logger.info("\n" + "=" * 60)
|
|
219
|
+
logger.info("Testing Non-Existent Variable (Should Return Default)")
|
|
220
|
+
logger.info("=" * 60)
|
|
221
|
+
|
|
222
|
+
# Test non-existent variable to demonstrate default handling
|
|
223
|
+
open_feature_client.get_string_details(
|
|
224
|
+
"doesnt-exist", "default fallback value", context
|
|
225
|
+
)
|
|
226
|
+
logger.info("Variable Key: doesnt-exist")
|
|
227
|
+
logger.info("Value: {nonexistent_details.value}")
|
|
228
|
+
logger.info("Reason: {nonexistent_details.reason}")
|
|
67
229
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
logger.info(
|
|
71
|
-
logger.info(f"Eval Reason: {details.reason}")
|
|
230
|
+
logger.info("\n" + "=" * 60)
|
|
231
|
+
logger.info("All tests completed!")
|
|
232
|
+
logger.info("=" * 60)
|
|
72
233
|
|
|
73
234
|
devcycle_client.close()
|
|
74
235
|
|
test/api/test_local_bucketing.py
CHANGED
|
@@ -98,7 +98,7 @@ class LocalBucketingTest(unittest.TestCase):
|
|
|
98
98
|
self.local_bucketing.set_platform_data(platform_json)
|
|
99
99
|
self.local_bucketing.init_event_queue(self.client_uuid, "{}")
|
|
100
100
|
user = DevCycleUser(user_id="test_user_id")
|
|
101
|
-
result = self.local_bucketing.get_variable_for_user_protobuf(
|
|
101
|
+
result, feature_id = self.local_bucketing.get_variable_for_user_protobuf(
|
|
102
102
|
user=user, key="string-var", default_value="default value"
|
|
103
103
|
)
|
|
104
104
|
self.assertIsNotNone(result)
|
|
@@ -106,6 +106,7 @@ class LocalBucketingTest(unittest.TestCase):
|
|
|
106
106
|
self.assertEqual(result.type, TypeEnum.STRING)
|
|
107
107
|
self.assertEqual(result.value, "variationOn")
|
|
108
108
|
self.assertFalse(result.isDefaulted)
|
|
109
|
+
self.assertIsNotNone(feature_id)
|
|
109
110
|
|
|
110
111
|
def test_get_variable_for_user_protobuf_special_characters(self):
|
|
111
112
|
self.local_bucketing.store_config(special_character_config())
|
|
@@ -113,7 +114,7 @@ class LocalBucketingTest(unittest.TestCase):
|
|
|
113
114
|
self.local_bucketing.set_platform_data(platform_json)
|
|
114
115
|
self.local_bucketing.init_event_queue(self.client_uuid, "{}")
|
|
115
116
|
user = DevCycleUser(user_id="test_user_id")
|
|
116
|
-
result = self.local_bucketing.get_variable_for_user_protobuf(
|
|
117
|
+
result, feature_id = self.local_bucketing.get_variable_for_user_protobuf(
|
|
117
118
|
user=user, key="string-var", default_value="default value"
|
|
118
119
|
)
|
|
119
120
|
self.assertIsNotNone(result)
|
|
@@ -121,6 +122,7 @@ class LocalBucketingTest(unittest.TestCase):
|
|
|
121
122
|
self.assertEqual(result.type, TypeEnum.STRING)
|
|
122
123
|
self.assertEqual(result.value, "öé 🐍 ¥ variationOn")
|
|
123
124
|
self.assertFalse(result.isDefaulted)
|
|
125
|
+
self.assertIsNotNone(feature_id)
|
|
124
126
|
|
|
125
127
|
def test_get_variable_for_user_protobuf_type_mismatch(self):
|
|
126
128
|
self.local_bucketing.store_config(small_config())
|
|
@@ -131,10 +133,11 @@ class LocalBucketingTest(unittest.TestCase):
|
|
|
131
133
|
|
|
132
134
|
# type mismatch is handled inside the WASM and will return
|
|
133
135
|
# no data if the type is not correct
|
|
134
|
-
result = self.local_bucketing.get_variable_for_user_protobuf(
|
|
136
|
+
result, feature_id = self.local_bucketing.get_variable_for_user_protobuf(
|
|
135
137
|
user=user, key="string-var", default_value=9999
|
|
136
138
|
)
|
|
137
139
|
self.assertIsNone(result)
|
|
140
|
+
self.assertIsNone(feature_id)
|
|
138
141
|
|
|
139
142
|
def test_generate_bucketed_config(self):
|
|
140
143
|
self.local_bucketing.store_config(small_config())
|
test/models/test_user.py
CHANGED
test/test_cloud_client.py
CHANGED
|
@@ -5,7 +5,6 @@ import uuid
|
|
|
5
5
|
from time import time
|
|
6
6
|
from unittest.mock import patch
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
from devcycle_python_sdk import DevCycleCloudClient, DevCycleCloudOptions
|
|
10
9
|
from devcycle_python_sdk.models.eval_hook import EvalHook
|
|
11
10
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
@@ -99,7 +98,7 @@ class DevCycleCloudClientTest(unittest.TestCase):
|
|
|
99
98
|
self.assertEqual(result.value, "default_value")
|
|
100
99
|
self.assertTrue(result.isDefaulted)
|
|
101
100
|
self.assertEqual(result.eval.reason, "DEFAULT")
|
|
102
|
-
self.assertEqual(result.eval.details, "
|
|
101
|
+
self.assertEqual(result.eval.details, "Error")
|
|
103
102
|
|
|
104
103
|
# other exception - return default
|
|
105
104
|
mock_variable_call.reset_mock()
|
|
@@ -307,10 +306,10 @@ class DevCycleCloudClientTest(unittest.TestCase):
|
|
|
307
306
|
hook_called["before"] = True
|
|
308
307
|
return context
|
|
309
308
|
|
|
310
|
-
def after_hook(context, variable):
|
|
309
|
+
def after_hook(context, variable, variable_metadata):
|
|
311
310
|
hook_called["after"] = True
|
|
312
311
|
|
|
313
|
-
def finally_hook(context, variable):
|
|
312
|
+
def finally_hook(context, variable, variable_metadata):
|
|
314
313
|
hook_called["finally"] = True
|
|
315
314
|
|
|
316
315
|
def error_hook(context, error):
|
|
@@ -347,10 +346,10 @@ class DevCycleCloudClientTest(unittest.TestCase):
|
|
|
347
346
|
hook_called["before"] = True
|
|
348
347
|
raise Exception("Before hook failed")
|
|
349
348
|
|
|
350
|
-
def after_hook(context, variable):
|
|
349
|
+
def after_hook(context, variable, variable_metadata):
|
|
351
350
|
hook_called["after"] = True
|
|
352
351
|
|
|
353
|
-
def finally_hook(context, variable):
|
|
352
|
+
def finally_hook(context, variable, variable_metadata):
|
|
354
353
|
hook_called["finally"] = True
|
|
355
354
|
|
|
356
355
|
def error_hook(context, error):
|
|
@@ -370,6 +369,42 @@ class DevCycleCloudClientTest(unittest.TestCase):
|
|
|
370
369
|
self.assertTrue(hook_called["finally"])
|
|
371
370
|
self.assertTrue(hook_called["error"])
|
|
372
371
|
|
|
372
|
+
@patch("devcycle_python_sdk.api.bucketing_client.BucketingAPIClient.variable")
|
|
373
|
+
def test_context_has_null_config_metadata(self, mock_variable_call):
|
|
374
|
+
mock_variable_call.return_value = Variable(
|
|
375
|
+
_id="123", key="strKey", value=999, type=TypeEnum.NUMBER
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
context_received = None
|
|
379
|
+
|
|
380
|
+
def before_hook(context):
|
|
381
|
+
nonlocal context_received
|
|
382
|
+
context_received = context
|
|
383
|
+
return context
|
|
384
|
+
|
|
385
|
+
def after_hook(context, variable, variable_metadata):
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
def finally_hook(context, variable, variable_metadata):
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
def error_hook(context, error):
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
self.test_client.add_hook(
|
|
395
|
+
EvalHook(before_hook, after_hook, finally_hook, error_hook)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Test that context has config_metadata field but it's null for cloud client
|
|
399
|
+
variable = self.test_client.variable(self.test_user, "strKey", 42)
|
|
400
|
+
|
|
401
|
+
# Verify the variable evaluation worked
|
|
402
|
+
self.assertIsNotNone(variable)
|
|
403
|
+
self.assertIsNotNone(context_received)
|
|
404
|
+
self.assertTrue(hasattr(context_received, "config_metadata"))
|
|
405
|
+
# Cloud client should have null config_metadata since it's not implemented
|
|
406
|
+
self.assertIsNone(context_received.config_metadata)
|
|
407
|
+
|
|
373
408
|
|
|
374
409
|
if __name__ == "__main__":
|
|
375
410
|
unittest.main()
|