devcycle-python-server-sdk 3.11.4__tar.gz → 3.12.0__tar.gz
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_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/PKG-INFO +1 -1
- devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/VERSION.txt +1 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/bucketing_client.py +12 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/local_bucketing.py +0 -8
- devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/cloud_client.py +12 -3
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/local_client.py +28 -13
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/sse_manager.py +1 -1
- devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/models/eval_reason.py +40 -0
- devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/models/variable.py +103 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/open_feature_provider/provider.py +10 -5
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/utils.py +18 -0
- devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +59 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +100 -78
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/PKG-INFO +1 -1
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/SOURCES.txt +1 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_local_bucketing.py +11 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/test_provider.py +79 -2
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/test_provider_local_sdk.py +25 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/test_cloud_client.py +7 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/test_local_client.py +16 -1
- devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/VERSION.txt +0 -1
- devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
- devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/models/variable.py +0 -65
- devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +0 -46
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/LICENSE +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/README.md +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/backoff.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/config_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/event_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/devcycle_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/exceptions.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/config_manager.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/eval_hooks_manager.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/event_queue_manager.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/bucketed_config.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/error_response.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/eval_hook.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/eval_hook_context.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/event.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/feature.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/platform_data.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/user.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/open_feature_provider/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/options.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/py.typed +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/strings.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/version.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/dependency_links.txt +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/requires.txt +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/top_level.txt +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/cloud_client_example.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/local_bucketing_client_example.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/openfeature_example.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/pyproject.toml +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/setup.cfg +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/setup.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_bucketing_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_config_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_event_client.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/fixture/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/fixture/data.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/test_config_manager.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/test_event_queue_manager.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/test_bucketed_config.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/test_user.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/__init__.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_strings.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_utils.py +0 -0
- {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.0
|
|
@@ -15,6 +15,7 @@ from devcycle_python_sdk.models.event import DevCycleEvent
|
|
|
15
15
|
from devcycle_python_sdk.models.feature import Feature
|
|
16
16
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
17
17
|
from devcycle_python_sdk.models.variable import Variable
|
|
18
|
+
from devcycle_python_sdk.models.eval_reason import EvalReason
|
|
18
19
|
from devcycle_python_sdk.util.strings import slash_join
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
@@ -91,11 +92,17 @@ class BucketingAPIClient:
|
|
|
91
92
|
def variable(self, key: str, user: DevCycleUser) -> Variable:
|
|
92
93
|
data = self.request("POST", self._url("variables", key), json=user.to_json())
|
|
93
94
|
|
|
95
|
+
eval_data = data.get("eval")
|
|
96
|
+
eval_reason = None
|
|
97
|
+
if eval_data is not None and isinstance(eval_data, dict):
|
|
98
|
+
eval_reason = EvalReason.from_json(eval_data)
|
|
99
|
+
|
|
94
100
|
return Variable(
|
|
95
101
|
_id=data.get("_id"),
|
|
96
102
|
key=data.get("key", ""),
|
|
97
103
|
type=data.get("type", ""),
|
|
98
104
|
value=data.get("value"),
|
|
105
|
+
eval=eval_reason,
|
|
99
106
|
)
|
|
100
107
|
|
|
101
108
|
def variables(self, user: DevCycleUser) -> Dict[str, Variable]:
|
|
@@ -109,6 +116,11 @@ class BucketingAPIClient:
|
|
|
109
116
|
type=str(value.get("type")),
|
|
110
117
|
value=value.get("value"),
|
|
111
118
|
isDefaulted=None,
|
|
119
|
+
eval=(
|
|
120
|
+
EvalReason.from_json(value.get("eval"))
|
|
121
|
+
if value.get("eval")
|
|
122
|
+
else None
|
|
123
|
+
),
|
|
112
124
|
)
|
|
113
125
|
|
|
114
126
|
return result
|
|
@@ -22,7 +22,6 @@ from wasmtime import (
|
|
|
22
22
|
import devcycle_python_sdk.protobuf.utils as pb_utils
|
|
23
23
|
import devcycle_python_sdk.protobuf.variableForUserParams_pb2 as pb2
|
|
24
24
|
from devcycle_python_sdk.exceptions import (
|
|
25
|
-
VariableTypeMismatchError,
|
|
26
25
|
MalformedConfigError,
|
|
27
26
|
)
|
|
28
27
|
from devcycle_python_sdk.models.bucketed_config import BucketedConfig
|
|
@@ -324,13 +323,6 @@ class LocalBucketing:
|
|
|
324
323
|
sdk_variable = pb2.SDKVariable_PB()
|
|
325
324
|
sdk_variable.ParseFromString(var_bytes)
|
|
326
325
|
|
|
327
|
-
if sdk_variable.type != pb_variable_type:
|
|
328
|
-
# this situation should never actually happen because the WASM handles
|
|
329
|
-
# it internally and returns a null value from the WASM function
|
|
330
|
-
# This check is here just in case that logic changes in the future
|
|
331
|
-
raise VariableTypeMismatchError(
|
|
332
|
-
f"Variable returned does not match requested type: {pb_variable_type}"
|
|
333
|
-
)
|
|
334
326
|
return pb_utils.create_variable(sdk_variable, default_value)
|
|
335
327
|
|
|
336
328
|
def generate_bucketed_config(self, user: DevCycleUser) -> BucketedConfig:
|
|
@@ -14,6 +14,9 @@ from devcycle_python_sdk.managers.eval_hooks_manager import (
|
|
|
14
14
|
BeforeHookError,
|
|
15
15
|
AfterHookError,
|
|
16
16
|
)
|
|
17
|
+
from devcycle_python_sdk.models.eval_reason import (
|
|
18
|
+
DefaultReasonDetails,
|
|
19
|
+
)
|
|
17
20
|
from devcycle_python_sdk.models.eval_hook import EvalHook
|
|
18
21
|
from devcycle_python_sdk.models.eval_hook_context import HookContext
|
|
19
22
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
@@ -121,7 +124,9 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
121
124
|
except NotFoundError:
|
|
122
125
|
logger.warning(f"DevCycle: Variable not found: {key}")
|
|
123
126
|
return Variable.create_default_variable(
|
|
124
|
-
key=key,
|
|
127
|
+
key=key,
|
|
128
|
+
default_value=default_value,
|
|
129
|
+
default_reason_detail=DefaultReasonDetails.MISSING_VARIABLE,
|
|
125
130
|
)
|
|
126
131
|
except BeforeHookError as e:
|
|
127
132
|
self.eval_hooks_manager.run_error(context, e)
|
|
@@ -130,7 +135,9 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
130
135
|
except Exception as e:
|
|
131
136
|
logger.error(f"DevCycle: Error evaluating variable: {e}")
|
|
132
137
|
return Variable.create_default_variable(
|
|
133
|
-
key=key,
|
|
138
|
+
key=key,
|
|
139
|
+
default_value=default_value,
|
|
140
|
+
default_reason_detail=DefaultReasonDetails.ERROR,
|
|
134
141
|
)
|
|
135
142
|
finally:
|
|
136
143
|
self.eval_hooks_manager.run_finally(context, variable)
|
|
@@ -143,7 +150,9 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
143
150
|
f"DevCycle: Variable {key} is type {type(variable.value)}, but default value is type {type(default_value)}",
|
|
144
151
|
)
|
|
145
152
|
return Variable.create_default_variable(
|
|
146
|
-
key=key,
|
|
153
|
+
key=key,
|
|
154
|
+
default_value=default_value,
|
|
155
|
+
default_reason_detail=DefaultReasonDetails.TYPE_MISMATCH,
|
|
147
156
|
)
|
|
148
157
|
|
|
149
158
|
return variable
|
|
@@ -6,7 +6,6 @@ from typing import Any, Dict, Union, Optional
|
|
|
6
6
|
|
|
7
7
|
from devcycle_python_sdk import DevCycleLocalOptions, AbstractDevCycleClient
|
|
8
8
|
from devcycle_python_sdk.api.local_bucketing import LocalBucketing
|
|
9
|
-
from devcycle_python_sdk.exceptions import VariableTypeMismatchError
|
|
10
9
|
from devcycle_python_sdk.managers.config_manager import EnvironmentConfigManager
|
|
11
10
|
from devcycle_python_sdk.managers.eval_hooks_manager import (
|
|
12
11
|
EvalHooksManager,
|
|
@@ -17,6 +16,11 @@ from devcycle_python_sdk.managers.event_queue_manager import EventQueueManager
|
|
|
17
16
|
from devcycle_python_sdk.models.bucketed_config import BucketedConfig
|
|
18
17
|
from devcycle_python_sdk.models.eval_hook import EvalHook
|
|
19
18
|
from devcycle_python_sdk.models.eval_hook_context import HookContext
|
|
19
|
+
from devcycle_python_sdk.models.eval_reason import (
|
|
20
|
+
DefaultReasonDetails,
|
|
21
|
+
EvalReason,
|
|
22
|
+
EvalReasons,
|
|
23
|
+
)
|
|
20
24
|
from devcycle_python_sdk.models.event import DevCycleEvent, EventType
|
|
21
25
|
from devcycle_python_sdk.models.feature import Feature
|
|
22
26
|
from devcycle_python_sdk.models.platform_data import default_platform_data
|
|
@@ -131,7 +135,10 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
131
135
|
try:
|
|
132
136
|
self.event_queue_manager.queue_aggregate_event(
|
|
133
137
|
event=DevCycleEvent(
|
|
134
|
-
type=EventType.AggVariableDefaulted,
|
|
138
|
+
type=EventType.AggVariableDefaulted,
|
|
139
|
+
target=key,
|
|
140
|
+
value=1,
|
|
141
|
+
metaData={"evalReason": EvalReasons.DEFAULT},
|
|
135
142
|
),
|
|
136
143
|
bucketed_config=None,
|
|
137
144
|
)
|
|
@@ -139,7 +146,9 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
139
146
|
logger.warning(
|
|
140
147
|
f"DevCycle: Unable to track AggVariableDefaulted event for Variable {key}: {e}"
|
|
141
148
|
)
|
|
142
|
-
return Variable.create_default_variable(
|
|
149
|
+
return Variable.create_default_variable(
|
|
150
|
+
key, default_value, DefaultReasonDetails.MISSING_CONFIG
|
|
151
|
+
)
|
|
143
152
|
|
|
144
153
|
context = HookContext(key, user, default_value)
|
|
145
154
|
variable = Variable.create_default_variable(
|
|
@@ -159,22 +168,28 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
159
168
|
)
|
|
160
169
|
if bucketed_variable is not None:
|
|
161
170
|
variable = bucketed_variable
|
|
171
|
+
else:
|
|
172
|
+
variable.eval = EvalReason(
|
|
173
|
+
reason=EvalReasons.DEFAULT,
|
|
174
|
+
details=DefaultReasonDetails.USER_NOT_TARGETED,
|
|
175
|
+
)
|
|
162
176
|
|
|
163
177
|
if before_hook_error is None:
|
|
164
178
|
self.eval_hooks_manager.run_after(context, variable)
|
|
165
179
|
else:
|
|
166
180
|
raise before_hook_error
|
|
167
|
-
except VariableTypeMismatchError:
|
|
168
|
-
logger.debug("DevCycle: Variable type mismatch, returning default value")
|
|
169
|
-
return variable
|
|
170
|
-
except BeforeHookError as e:
|
|
171
|
-
self.eval_hooks_manager.run_error(context, e)
|
|
172
|
-
return variable
|
|
173
|
-
except AfterHookError as e:
|
|
174
|
-
self.eval_hooks_manager.run_error(context, e)
|
|
175
|
-
return variable
|
|
176
181
|
except Exception as e:
|
|
177
|
-
|
|
182
|
+
variable.eval = EvalReason(
|
|
183
|
+
reason=EvalReasons.DEFAULT, details=DefaultReasonDetails.ERROR
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if isinstance(e, BeforeHookError):
|
|
187
|
+
self.eval_hooks_manager.run_error(context, e)
|
|
188
|
+
elif isinstance(e, AfterHookError):
|
|
189
|
+
self.eval_hooks_manager.run_error(context, e)
|
|
190
|
+
else:
|
|
191
|
+
logger.warning(f"DevCycle: Error retrieving variable for user: {e}")
|
|
192
|
+
|
|
178
193
|
return variable
|
|
179
194
|
finally:
|
|
180
195
|
self.eval_hooks_manager.run_finally(context, variable)
|
|
@@ -43,7 +43,7 @@ class SSEManager:
|
|
|
43
43
|
elif isinstance(event, ld_eventsource.actions.Event):
|
|
44
44
|
handle_message(event)
|
|
45
45
|
except Exception as e:
|
|
46
|
-
logger.
|
|
46
|
+
logger.debug(f"DevCycle: failed to read SSE message: {e}")
|
|
47
47
|
|
|
48
48
|
def update(self, config: dict):
|
|
49
49
|
if self.use_new_config(config["sse"]):
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EvalReasons:
|
|
6
|
+
"""Evaluation reasons constants"""
|
|
7
|
+
|
|
8
|
+
DEFAULT = "DEFAULT"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DefaultReasonDetails:
|
|
12
|
+
"""Default reason details constants"""
|
|
13
|
+
|
|
14
|
+
MISSING_CONFIG = "Missing Config"
|
|
15
|
+
USER_NOT_TARGETED = "User Not Targeted"
|
|
16
|
+
TYPE_MISMATCH = "Variable Type Mismatch"
|
|
17
|
+
MISSING_VARIABLE = "Missing Variable"
|
|
18
|
+
ERROR = "Error"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(order=False)
|
|
22
|
+
class EvalReason:
|
|
23
|
+
reason: str
|
|
24
|
+
details: Optional[str] = None
|
|
25
|
+
target_id: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
def to_json(self):
|
|
28
|
+
return {
|
|
29
|
+
key: getattr(self, key)
|
|
30
|
+
for key in self.__dataclass_fields__
|
|
31
|
+
if getattr(self, key) is not None
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_json(cls, data: dict) -> "EvalReason":
|
|
36
|
+
return cls(
|
|
37
|
+
reason=data["reason"],
|
|
38
|
+
details=data.get("details"),
|
|
39
|
+
target_id=data.get("target_id"),
|
|
40
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# ruff: noqa: N815
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
|
|
5
|
+
from .eval_reason import EvalReason, EvalReasons
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TypeEnum:
|
|
9
|
+
BOOLEAN = "Boolean"
|
|
10
|
+
STRING = "String"
|
|
11
|
+
NUMBER = "Number"
|
|
12
|
+
JSON = "JSON"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def determine_variable_type(value: Any) -> str:
|
|
16
|
+
if isinstance(value, bool):
|
|
17
|
+
return TypeEnum.BOOLEAN
|
|
18
|
+
elif isinstance(value, str):
|
|
19
|
+
return TypeEnum.STRING
|
|
20
|
+
elif isinstance(value, (int, float)):
|
|
21
|
+
return TypeEnum.NUMBER
|
|
22
|
+
elif isinstance(value, dict):
|
|
23
|
+
return TypeEnum.JSON
|
|
24
|
+
else:
|
|
25
|
+
raise TypeError(f"Unsupported type: {type(value)}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(order=False)
|
|
29
|
+
class Variable:
|
|
30
|
+
_id: Optional[str]
|
|
31
|
+
key: str
|
|
32
|
+
type: str
|
|
33
|
+
value: Any = None
|
|
34
|
+
isDefaulted: Optional[bool] = False
|
|
35
|
+
defaultValue: Any = None
|
|
36
|
+
evalReason: Optional[str] = None
|
|
37
|
+
eval: Optional[EvalReason] = None
|
|
38
|
+
|
|
39
|
+
def to_json(self):
|
|
40
|
+
result = {}
|
|
41
|
+
for key in self.__dataclass_fields__:
|
|
42
|
+
value = getattr(self, key)
|
|
43
|
+
if value is not None:
|
|
44
|
+
if key == "eval" and isinstance(value, EvalReason):
|
|
45
|
+
result[key] = value.to_json()
|
|
46
|
+
else:
|
|
47
|
+
result[key] = value
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_json(cls, data: dict) -> "Variable":
|
|
52
|
+
eval_data = data.get("eval")
|
|
53
|
+
eval_reason = None
|
|
54
|
+
if eval_data:
|
|
55
|
+
eval_reason = EvalReason.from_json(eval_data)
|
|
56
|
+
|
|
57
|
+
return cls(
|
|
58
|
+
_id=data["_id"],
|
|
59
|
+
key=data["key"],
|
|
60
|
+
type=data["type"],
|
|
61
|
+
value=data["value"],
|
|
62
|
+
isDefaulted=data.get("isDefaulted", None),
|
|
63
|
+
defaultValue=data.get("defaultValue"),
|
|
64
|
+
evalReason=data.get("evalReason"),
|
|
65
|
+
eval=eval_reason,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def create_default_variable(
|
|
70
|
+
key: str, default_value: Any, default_reason_detail: Optional[str] = None
|
|
71
|
+
) -> "Variable":
|
|
72
|
+
var_type = determine_variable_type(default_value)
|
|
73
|
+
if default_reason_detail is not None:
|
|
74
|
+
eval_reason = EvalReason(
|
|
75
|
+
reason=EvalReasons.DEFAULT, details=default_reason_detail
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
eval_reason = None
|
|
79
|
+
return Variable(
|
|
80
|
+
_id=None,
|
|
81
|
+
key=key,
|
|
82
|
+
type=var_type,
|
|
83
|
+
value=default_value,
|
|
84
|
+
defaultValue=default_value,
|
|
85
|
+
isDefaulted=True,
|
|
86
|
+
eval=eval_reason,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def get_flag_meta_data(self) -> dict:
|
|
90
|
+
"""
|
|
91
|
+
Returns metadata dictionary for OpenFeature flag resolution.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict: Dictionary containing evalReasonDetails and evalReasonTargetId
|
|
95
|
+
if they exist, empty dict otherwise.
|
|
96
|
+
"""
|
|
97
|
+
meta_data = {}
|
|
98
|
+
if self.eval:
|
|
99
|
+
if self.eval.details:
|
|
100
|
+
meta_data["evalReasonDetails"] = self.eval.details
|
|
101
|
+
if self.eval.target_id:
|
|
102
|
+
meta_data["evalReasonTargetId"] = self.eval.target_id
|
|
103
|
+
return meta_data
|
|
@@ -80,13 +80,18 @@ class DevCycleProvider(AbstractProvider):
|
|
|
80
80
|
reason=Reason.DEFAULT,
|
|
81
81
|
)
|
|
82
82
|
else:
|
|
83
|
+
# TODO: once eval enabled from cloud bucketing, eval reason won't be null unless defaulted
|
|
84
|
+
if variable.eval and variable.eval.reason:
|
|
85
|
+
reason = variable.eval.reason
|
|
86
|
+
elif variable.isDefaulted:
|
|
87
|
+
reason = Reason.DEFAULT
|
|
88
|
+
else:
|
|
89
|
+
reason = Reason.TARGETING_MATCH
|
|
90
|
+
|
|
83
91
|
return FlagResolutionDetails(
|
|
84
92
|
value=variable.value,
|
|
85
|
-
reason=
|
|
86
|
-
|
|
87
|
-
if variable.isDefaulted
|
|
88
|
-
else Reason.TARGETING_MATCH
|
|
89
|
-
),
|
|
93
|
+
reason=reason,
|
|
94
|
+
flag_metadata=variable.get_flag_meta_data(),
|
|
90
95
|
)
|
|
91
96
|
except ValueError as e:
|
|
92
97
|
# occurs if the key or default value is None
|
|
@@ -5,6 +5,7 @@ import math
|
|
|
5
5
|
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
from devcycle_python_sdk.models.variable import TypeEnum, Variable
|
|
8
|
+
from devcycle_python_sdk.models.eval_reason import EvalReason
|
|
8
9
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
9
10
|
|
|
10
11
|
import devcycle_python_sdk.protobuf.variableForUserParams_pb2 as pb2
|
|
@@ -82,7 +83,20 @@ def create_dvcuser_pb(user: DevCycleUser) -> pb2.DVCUser_PB: # type: ignore
|
|
|
82
83
|
)
|
|
83
84
|
|
|
84
85
|
|
|
86
|
+
def create_eval_reason_from_pb(eval_reason_pb: pb2.EvalReason_PB) -> EvalReason: # type: ignore
|
|
87
|
+
"""Convert EvalReason_PB protobuf message to EvalReason object"""
|
|
88
|
+
return EvalReason(
|
|
89
|
+
reason=eval_reason_pb.reason,
|
|
90
|
+
details=eval_reason_pb.details if eval_reason_pb.details else None,
|
|
91
|
+
target_id=eval_reason_pb.target_id if eval_reason_pb.target_id else None,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
85
95
|
def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Variable: # type: ignore
|
|
96
|
+
eval_reason_obj = None
|
|
97
|
+
if sdk_variable.HasField("eval"):
|
|
98
|
+
eval_reason_obj = create_eval_reason_from_pb(sdk_variable.eval)
|
|
99
|
+
|
|
86
100
|
if sdk_variable.type == pb2.VariableType_PB.Boolean: # type: ignore
|
|
87
101
|
return Variable(
|
|
88
102
|
_id=None,
|
|
@@ -91,6 +105,7 @@ def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Var
|
|
|
91
105
|
type=TypeEnum.BOOLEAN,
|
|
92
106
|
isDefaulted=False,
|
|
93
107
|
defaultValue=default_value,
|
|
108
|
+
eval=eval_reason_obj,
|
|
94
109
|
)
|
|
95
110
|
|
|
96
111
|
elif sdk_variable.type == pb2.VariableType_PB.String: # type: ignore
|
|
@@ -101,6 +116,7 @@ def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Var
|
|
|
101
116
|
type=TypeEnum.STRING,
|
|
102
117
|
isDefaulted=False,
|
|
103
118
|
defaultValue=default_value,
|
|
119
|
+
eval=eval_reason_obj,
|
|
104
120
|
)
|
|
105
121
|
|
|
106
122
|
elif sdk_variable.type == pb2.VariableType_PB.Number: # type: ignore
|
|
@@ -111,6 +127,7 @@ def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Var
|
|
|
111
127
|
type=TypeEnum.NUMBER,
|
|
112
128
|
isDefaulted=False,
|
|
113
129
|
defaultValue=default_value,
|
|
130
|
+
eval=eval_reason_obj,
|
|
114
131
|
)
|
|
115
132
|
|
|
116
133
|
elif sdk_variable.type == pb2.VariableType_PB.JSON: # type: ignore
|
|
@@ -123,6 +140,7 @@ def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Var
|
|
|
123
140
|
type=TypeEnum.JSON,
|
|
124
141
|
isDefaulted=False,
|
|
125
142
|
defaultValue=default_value,
|
|
143
|
+
eval=eval_reason_obj,
|
|
126
144
|
)
|
|
127
145
|
|
|
128
146
|
else:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: variableForUserParams.proto
|
|
5
|
+
# Protobuf Python Version: 5.29.3
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
5,
|
|
15
|
+
29,
|
|
16
|
+
3,
|
|
17
|
+
'',
|
|
18
|
+
'variableForUserParams.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
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\"\xed\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\x12!\n\x08_feature\x18\x08 \x01(\x0b\x32\x0f.NullableString\x12\x1c\n\x04\x65val\x18\t \x01(\x0b\x32\x0e.EvalReason_PB\"C\n\rEvalReason_PB\x12\x0e\n\x06reason\x18\x01 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x02 \x01(\t\x12\x11\n\ttarget_id\x18\x03 \x01(\t*@\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')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'variableForUserParams_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
34
|
+
_globals['DESCRIPTOR']._serialized_options = b'\n&com.devcycle.sdk.server.local.protobufP\001Z\007./proto\252\002\"DevCycle.SDK.Server.Local.Protobuf'
|
|
35
|
+
_globals['_NULLABLECUSTOMDATA_VALUEENTRY']._loaded_options = None
|
|
36
|
+
_globals['_NULLABLECUSTOMDATA_VALUEENTRY']._serialized_options = b'8\001'
|
|
37
|
+
_globals['_VARIABLETYPE_PB']._serialized_start=1221
|
|
38
|
+
_globals['_VARIABLETYPE_PB']._serialized_end=1285
|
|
39
|
+
_globals['_CUSTOMDATATYPE']._serialized_start=1287
|
|
40
|
+
_globals['_CUSTOMDATATYPE']._serialized_end=1341
|
|
41
|
+
_globals['_NULLABLESTRING']._serialized_start=31
|
|
42
|
+
_globals['_NULLABLESTRING']._serialized_end=78
|
|
43
|
+
_globals['_NULLABLEDOUBLE']._serialized_start=80
|
|
44
|
+
_globals['_NULLABLEDOUBLE']._serialized_end=127
|
|
45
|
+
_globals['_CUSTOMDATAVALUE']._serialized_start=129
|
|
46
|
+
_globals['_CUSTOMDATAVALUE']._serialized_end=238
|
|
47
|
+
_globals['_NULLABLECUSTOMDATA']._serialized_start=241
|
|
48
|
+
_globals['_NULLABLECUSTOMDATA']._serialized_end=388
|
|
49
|
+
_globals['_NULLABLECUSTOMDATA_VALUEENTRY']._serialized_start=326
|
|
50
|
+
_globals['_NULLABLECUSTOMDATA_VALUEENTRY']._serialized_end=388
|
|
51
|
+
_globals['_VARIABLEFORUSERPARAMS_PB']._serialized_start=391
|
|
52
|
+
_globals['_VARIABLEFORUSERPARAMS_PB']._serialized_end=547
|
|
53
|
+
_globals['_DVCUSER_PB']._serialized_start=550
|
|
54
|
+
_globals['_DVCUSER_PB']._serialized_end=910
|
|
55
|
+
_globals['_SDKVARIABLE_PB']._serialized_start=913
|
|
56
|
+
_globals['_SDKVARIABLE_PB']._serialized_end=1150
|
|
57
|
+
_globals['_EVALREASON_PB']._serialized_start=1152
|
|
58
|
+
_globals['_EVALREASON_PB']._serialized_end=1219
|
|
59
|
+
# @@protoc_insertion_point(module_scope)
|