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.
Files changed (82) hide show
  1. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/PKG-INFO +1 -1
  2. devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/VERSION.txt +1 -0
  3. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/bucketing_client.py +12 -0
  4. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/local_bucketing.py +0 -8
  5. devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
  6. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/cloud_client.py +12 -3
  7. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/local_client.py +28 -13
  8. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/sse_manager.py +1 -1
  9. devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/models/eval_reason.py +40 -0
  10. devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/models/variable.py +103 -0
  11. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/open_feature_provider/provider.py +10 -5
  12. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/utils.py +18 -0
  13. devcycle_python_server_sdk-3.12.0/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +59 -0
  14. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +100 -78
  15. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/PKG-INFO +1 -1
  16. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/SOURCES.txt +1 -0
  17. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_local_bucketing.py +11 -0
  18. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/test_provider.py +79 -2
  19. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/test_provider_local_sdk.py +25 -0
  20. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/test_cloud_client.py +7 -0
  21. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/test_local_client.py +16 -1
  22. devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/VERSION.txt +0 -1
  23. devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
  24. devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/models/variable.py +0 -65
  25. devcycle_python_server_sdk-3.11.4/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +0 -46
  26. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/LICENSE +0 -0
  27. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/README.md +0 -0
  28. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/__init__.py +0 -0
  29. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/__init__.py +0 -0
  30. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/backoff.py +0 -0
  31. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/config_client.py +0 -0
  32. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/api/event_client.py +0 -0
  33. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/devcycle_client.py +0 -0
  34. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/exceptions.py +0 -0
  35. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/__init__.py +0 -0
  36. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/config_manager.py +0 -0
  37. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/eval_hooks_manager.py +0 -0
  38. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/managers/event_queue_manager.py +0 -0
  39. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/__init__.py +0 -0
  40. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/bucketed_config.py +0 -0
  41. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/error_response.py +0 -0
  42. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/eval_hook.py +0 -0
  43. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/eval_hook_context.py +0 -0
  44. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/event.py +0 -0
  45. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/feature.py +0 -0
  46. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/platform_data.py +0 -0
  47. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/models/user.py +0 -0
  48. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/open_feature_provider/__init__.py +0 -0
  49. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/options.py +0 -0
  50. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/protobuf/__init__.py +0 -0
  51. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/py.typed +0 -0
  52. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/__init__.py +0 -0
  53. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/strings.py +0 -0
  54. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_sdk/util/version.py +0 -0
  55. {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
  56. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/devcycle_python_server_sdk.egg-info/requires.txt +0 -0
  57. {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
  58. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/__init__.py +0 -0
  59. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/cloud_client_example.py +0 -0
  60. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/local_bucketing_client_example.py +0 -0
  61. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/example/openfeature_example.py +0 -0
  62. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/pyproject.toml +0 -0
  63. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/setup.cfg +0 -0
  64. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/setup.py +0 -0
  65. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/__init__.py +0 -0
  66. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/__init__.py +0 -0
  67. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_bucketing_client.py +0 -0
  68. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_config_client.py +0 -0
  69. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/api/test_event_client.py +0 -0
  70. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/fixture/__init__.py +0 -0
  71. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/fixture/data.py +0 -0
  72. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/__init__.py +0 -0
  73. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/test_config_manager.py +0 -0
  74. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/managers/test_event_queue_manager.py +0 -0
  75. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/__init__.py +0 -0
  76. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/test_bucketed_config.py +0 -0
  77. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/models/test_user.py +0 -0
  78. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/openfeature_test/__init__.py +0 -0
  79. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/__init__.py +0 -0
  80. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_strings.py +0 -0
  81. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_utils.py +0 -0
  82. {devcycle_python_server_sdk-3.11.4 → devcycle_python_server_sdk-3.12.0}/test/util/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcycle-python-server-sdk
3
- Version: 3.11.4
3
+ Version: 3.12.0
4
4
  Summary: DevCycle Python SDK
5
5
  Home-page: https://github.com/devcycleHQ/python-server-sdk
6
6
  Author-email:
@@ -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, default_value=default_value
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, default_value=default_value
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, default_value=default_value
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, target=key, value=1
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(key, default_value)
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
- logger.warning(f"DevCycle: Error retrieving variable for user: {e}")
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.exception(f"DevCycle: failed to read SSE message: {e}")
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
- Reason.DEFAULT
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)