devcycle-python-server-sdk 3.11.4__py3-none-any.whl → 3.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 3.11.4
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, 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
+ )
@@ -2,6 +2,8 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import Optional, Any
4
4
 
5
+ from .eval_reason import EvalReason, EvalReasons
6
+
5
7
 
6
8
  class TypeEnum:
7
9
  BOOLEAN = "Boolean"
@@ -32,16 +34,26 @@ class Variable:
32
34
  isDefaulted: Optional[bool] = False
33
35
  defaultValue: Any = None
34
36
  evalReason: Optional[str] = None
37
+ eval: Optional[EvalReason] = None
35
38
 
36
39
  def to_json(self):
37
- return {
38
- key: getattr(self, key)
39
- for key in self.__dataclass_fields__
40
- if getattr(self, key) is not None
41
- }
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
42
49
 
43
50
  @classmethod
44
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
+
45
57
  return cls(
46
58
  _id=data["_id"],
47
59
  key=data["key"],
@@ -50,11 +62,20 @@ class Variable:
50
62
  isDefaulted=data.get("isDefaulted", None),
51
63
  defaultValue=data.get("defaultValue"),
52
64
  evalReason=data.get("evalReason"),
65
+ eval=eval_reason,
53
66
  )
54
67
 
55
68
  @staticmethod
56
- def create_default_variable(key: str, default_value: Any) -> "Variable":
69
+ def create_default_variable(
70
+ key: str, default_value: Any, default_reason_detail: Optional[str] = None
71
+ ) -> "Variable":
57
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
58
79
  return Variable(
59
80
  _id=None,
60
81
  key=key,
@@ -62,4 +83,21 @@ class Variable:
62
83
  value=default_value,
63
84
  defaultValue=default_value,
64
85
  isDefaulted=True,
86
+ eval=eval_reason,
65
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:
@@ -1,11 +1,22 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
3
4
  # source: variableForUserParams.proto
5
+ # Protobuf Python Version: 5.29.3
4
6
  """Generated protocol buffer code."""
5
- from google.protobuf.internal import builder as _builder
6
7
  from google.protobuf import descriptor as _descriptor
7
8
  from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
8
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
+ )
9
20
  # @@protoc_insertion_point(imports)
10
21
 
11
22
  _sym_db = _symbol_database.Default()
@@ -13,34 +24,36 @@ _sym_db = _symbol_database.Default()
13
24
 
14
25
 
15
26
 
16
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bvariableForUserParams.proto\"/\n\x0eNullableString\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"/\n\x0eNullableDouble\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"m\n\x0f\x43ustomDataValue\x12\x1d\n\x04type\x18\x01 \x01(\x0e\x32\x0f.CustomDataType\x12\x11\n\tboolValue\x18\x02 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x03 \x01(\x01\x12\x13\n\x0bstringValue\x18\x04 \x01(\t\"\x93\x01\n\x12NullableCustomData\x12-\n\x05value\x18\x01 \x03(\x0b\x32\x1e.NullableCustomData.ValueEntry\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\x1a>\n\nValueEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1f\n\x05value\x18\x02 \x01(\x0b\x32\x10.CustomDataValue:\x02\x38\x01\"\x9c\x01\n\x18VariableForUserParams_PB\x12\x0e\n\x06sdkKey\x18\x01 \x01(\t\x12\x13\n\x0bvariableKey\x18\x02 \x01(\t\x12&\n\x0cvariableType\x18\x03 \x01(\x0e\x32\x10.VariableType_PB\x12\x19\n\x04user\x18\x04 \x01(\x0b\x32\x0b.DVCUser_PB\x12\x18\n\x10shouldTrackEvent\x18\x05 \x01(\x08\"\xe8\x02\n\nDVCUser_PB\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1e\n\x05\x65mail\x18\x02 \x01(\x0b\x32\x0f.NullableString\x12\x1d\n\x04name\x18\x03 \x01(\x0b\x32\x0f.NullableString\x12!\n\x08language\x18\x04 \x01(\x0b\x32\x0f.NullableString\x12 \n\x07\x63ountry\x18\x05 \x01(\x0b\x32\x0f.NullableString\x12!\n\x08\x61ppBuild\x18\x06 \x01(\x0b\x32\x0f.NullableDouble\x12#\n\nappVersion\x18\x07 \x01(\x0b\x32\x0f.NullableString\x12$\n\x0b\x64\x65viceModel\x18\x08 \x01(\x0b\x32\x0f.NullableString\x12\'\n\ncustomData\x18\t \x01(\x0b\x32\x13.NullableCustomData\x12.\n\x11privateCustomData\x18\n \x01(\x0b\x32\x13.NullableCustomData\"\xac\x01\n\x0eSDKVariable_PB\x12\x0b\n\x03_id\x18\x01 \x01(\t\x12\x1e\n\x04type\x18\x02 \x01(\x0e\x32\x10.VariableType_PB\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x11\n\tboolValue\x18\x04 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x05 \x01(\x01\x12\x13\n\x0bstringValue\x18\x06 \x01(\t\x12#\n\nevalReason\x18\x07 \x01(\x0b\x32\x0f.NullableString*@\n\x0fVariableType_PB\x12\x0b\n\x07\x42oolean\x10\x00\x12\n\n\x06Number\x10\x01\x12\n\n\x06String\x10\x02\x12\x08\n\x04JSON\x10\x03*6\n\x0e\x43ustomDataType\x12\x08\n\x04\x42ool\x10\x00\x12\x07\n\x03Num\x10\x01\x12\x07\n\x03Str\x10\x02\x12\x08\n\x04Null\x10\x03\x42X\n&com.devcycle.sdk.server.local.protobufP\x01Z\x07./proto\xaa\x02\"DevCycle.SDK.Server.Local.Protobufb\x06proto3')
17
-
18
- _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
19
- _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'variableForUserParams_pb2', globals())
20
- if _descriptor._USE_C_DESCRIPTORS == False:
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')
21
28
 
22
- DESCRIPTOR._options = None
23
- DESCRIPTOR._serialized_options = b'\n&com.devcycle.sdk.server.local.protobufP\001Z\007./proto\252\002\"DevCycle.SDK.Server.Local.Protobuf'
24
- _NULLABLECUSTOMDATA_VALUEENTRY._options = None
25
- _NULLABLECUSTOMDATA_VALUEENTRY._serialized_options = b'8\001'
26
- _VARIABLETYPE_PB._serialized_start=1087
27
- _VARIABLETYPE_PB._serialized_end=1151
28
- _CUSTOMDATATYPE._serialized_start=1153
29
- _CUSTOMDATATYPE._serialized_end=1207
30
- _NULLABLESTRING._serialized_start=31
31
- _NULLABLESTRING._serialized_end=78
32
- _NULLABLEDOUBLE._serialized_start=80
33
- _NULLABLEDOUBLE._serialized_end=127
34
- _CUSTOMDATAVALUE._serialized_start=129
35
- _CUSTOMDATAVALUE._serialized_end=238
36
- _NULLABLECUSTOMDATA._serialized_start=241
37
- _NULLABLECUSTOMDATA._serialized_end=388
38
- _NULLABLECUSTOMDATA_VALUEENTRY._serialized_start=326
39
- _NULLABLECUSTOMDATA_VALUEENTRY._serialized_end=388
40
- _VARIABLEFORUSERPARAMS_PB._serialized_start=391
41
- _VARIABLEFORUSERPARAMS_PB._serialized_end=547
42
- _DVCUSER_PB._serialized_start=550
43
- _DVCUSER_PB._serialized_end=910
44
- _SDKVARIABLE_PB._serialized_start=913
45
- _SDKVARIABLE_PB._serialized_end=1085
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
46
59
  # @@protoc_insertion_point(module_scope)
@@ -4,117 +4,139 @@ from google.protobuf import descriptor as _descriptor
4
4
  from google.protobuf import message as _message
5
5
  from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
6
6
 
7
- Bool: CustomDataType
8
- Boolean: VariableType_PB
9
7
  DESCRIPTOR: _descriptor.FileDescriptor
8
+
9
+ class VariableType_PB(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
10
+ __slots__ = ()
11
+ Boolean: _ClassVar[VariableType_PB]
12
+ Number: _ClassVar[VariableType_PB]
13
+ String: _ClassVar[VariableType_PB]
14
+ JSON: _ClassVar[VariableType_PB]
15
+
16
+ class CustomDataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
17
+ __slots__ = ()
18
+ Bool: _ClassVar[CustomDataType]
19
+ Num: _ClassVar[CustomDataType]
20
+ Str: _ClassVar[CustomDataType]
21
+ Null: _ClassVar[CustomDataType]
22
+ Boolean: VariableType_PB
23
+ Number: VariableType_PB
24
+ String: VariableType_PB
10
25
  JSON: VariableType_PB
11
- Null: CustomDataType
26
+ Bool: CustomDataType
12
27
  Num: CustomDataType
13
- Number: VariableType_PB
14
28
  Str: CustomDataType
15
- String: VariableType_PB
29
+ Null: CustomDataType
30
+
31
+ class NullableString(_message.Message):
32
+ __slots__ = ("value", "isNull")
33
+ VALUE_FIELD_NUMBER: _ClassVar[int]
34
+ ISNULL_FIELD_NUMBER: _ClassVar[int]
35
+ value: str
36
+ isNull: bool
37
+ def __init__(self, value: _Optional[str] = ..., isNull: bool = ...) -> None: ...
38
+
39
+ class NullableDouble(_message.Message):
40
+ __slots__ = ("value", "isNull")
41
+ VALUE_FIELD_NUMBER: _ClassVar[int]
42
+ ISNULL_FIELD_NUMBER: _ClassVar[int]
43
+ value: float
44
+ isNull: bool
45
+ def __init__(self, value: _Optional[float] = ..., isNull: bool = ...) -> None: ...
16
46
 
17
47
  class CustomDataValue(_message.Message):
18
- __slots__ = ["boolValue", "doubleValue", "stringValue", "type"]
48
+ __slots__ = ("type", "boolValue", "doubleValue", "stringValue")
49
+ TYPE_FIELD_NUMBER: _ClassVar[int]
19
50
  BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
20
51
  DOUBLEVALUE_FIELD_NUMBER: _ClassVar[int]
21
52
  STRINGVALUE_FIELD_NUMBER: _ClassVar[int]
22
- TYPE_FIELD_NUMBER: _ClassVar[int]
53
+ type: CustomDataType
23
54
  boolValue: bool
24
55
  doubleValue: float
25
56
  stringValue: str
26
- type: CustomDataType
27
57
  def __init__(self, type: _Optional[_Union[CustomDataType, str]] = ..., boolValue: bool = ..., doubleValue: _Optional[float] = ..., stringValue: _Optional[str] = ...) -> None: ...
28
58
 
29
- class DVCUser_PB(_message.Message):
30
- __slots__ = ["appBuild", "appVersion", "country", "customData", "deviceModel", "email", "language", "name", "privateCustomData", "user_id"]
31
- APPBUILD_FIELD_NUMBER: _ClassVar[int]
32
- APPVERSION_FIELD_NUMBER: _ClassVar[int]
33
- COUNTRY_FIELD_NUMBER: _ClassVar[int]
34
- CUSTOMDATA_FIELD_NUMBER: _ClassVar[int]
35
- DEVICEMODEL_FIELD_NUMBER: _ClassVar[int]
36
- EMAIL_FIELD_NUMBER: _ClassVar[int]
37
- LANGUAGE_FIELD_NUMBER: _ClassVar[int]
38
- NAME_FIELD_NUMBER: _ClassVar[int]
39
- PRIVATECUSTOMDATA_FIELD_NUMBER: _ClassVar[int]
40
- USER_ID_FIELD_NUMBER: _ClassVar[int]
41
- appBuild: NullableDouble
42
- appVersion: NullableString
43
- country: NullableString
44
- customData: NullableCustomData
45
- deviceModel: NullableString
46
- email: NullableString
47
- language: NullableString
48
- name: NullableString
49
- privateCustomData: NullableCustomData
50
- user_id: str
51
- 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: ...
52
-
53
59
  class NullableCustomData(_message.Message):
54
- __slots__ = ["isNull", "value"]
60
+ __slots__ = ("value", "isNull")
55
61
  class ValueEntry(_message.Message):
56
- __slots__ = ["key", "value"]
62
+ __slots__ = ("key", "value")
57
63
  KEY_FIELD_NUMBER: _ClassVar[int]
58
64
  VALUE_FIELD_NUMBER: _ClassVar[int]
59
65
  key: str
60
66
  value: CustomDataValue
61
67
  def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[CustomDataValue, _Mapping]] = ...) -> None: ...
62
- ISNULL_FIELD_NUMBER: _ClassVar[int]
63
68
  VALUE_FIELD_NUMBER: _ClassVar[int]
64
- isNull: bool
69
+ ISNULL_FIELD_NUMBER: _ClassVar[int]
65
70
  value: _containers.MessageMap[str, CustomDataValue]
71
+ isNull: bool
66
72
  def __init__(self, value: _Optional[_Mapping[str, CustomDataValue]] = ..., isNull: bool = ...) -> None: ...
67
73
 
68
- class NullableDouble(_message.Message):
69
- __slots__ = ["isNull", "value"]
70
- ISNULL_FIELD_NUMBER: _ClassVar[int]
71
- VALUE_FIELD_NUMBER: _ClassVar[int]
72
- isNull: bool
73
- value: float
74
- def __init__(self, value: _Optional[float] = ..., isNull: bool = ...) -> None: ...
74
+ class VariableForUserParams_PB(_message.Message):
75
+ __slots__ = ("sdkKey", "variableKey", "variableType", "user", "shouldTrackEvent")
76
+ SDKKEY_FIELD_NUMBER: _ClassVar[int]
77
+ VARIABLEKEY_FIELD_NUMBER: _ClassVar[int]
78
+ VARIABLETYPE_FIELD_NUMBER: _ClassVar[int]
79
+ USER_FIELD_NUMBER: _ClassVar[int]
80
+ SHOULDTRACKEVENT_FIELD_NUMBER: _ClassVar[int]
81
+ sdkKey: str
82
+ variableKey: str
83
+ variableType: VariableType_PB
84
+ user: DVCUser_PB
85
+ shouldTrackEvent: bool
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: ...
75
87
 
76
- class NullableString(_message.Message):
77
- __slots__ = ["isNull", "value"]
78
- ISNULL_FIELD_NUMBER: _ClassVar[int]
79
- VALUE_FIELD_NUMBER: _ClassVar[int]
80
- isNull: bool
81
- value: str
82
- def __init__(self, value: _Optional[str] = ..., isNull: bool = ...) -> None: ...
88
+ class DVCUser_PB(_message.Message):
89
+ __slots__ = ("user_id", "email", "name", "language", "country", "appBuild", "appVersion", "deviceModel", "customData", "privateCustomData")
90
+ USER_ID_FIELD_NUMBER: _ClassVar[int]
91
+ EMAIL_FIELD_NUMBER: _ClassVar[int]
92
+ NAME_FIELD_NUMBER: _ClassVar[int]
93
+ LANGUAGE_FIELD_NUMBER: _ClassVar[int]
94
+ COUNTRY_FIELD_NUMBER: _ClassVar[int]
95
+ APPBUILD_FIELD_NUMBER: _ClassVar[int]
96
+ APPVERSION_FIELD_NUMBER: _ClassVar[int]
97
+ DEVICEMODEL_FIELD_NUMBER: _ClassVar[int]
98
+ CUSTOMDATA_FIELD_NUMBER: _ClassVar[int]
99
+ PRIVATECUSTOMDATA_FIELD_NUMBER: _ClassVar[int]
100
+ user_id: str
101
+ email: NullableString
102
+ name: NullableString
103
+ language: NullableString
104
+ country: NullableString
105
+ appBuild: NullableDouble
106
+ appVersion: NullableString
107
+ deviceModel: NullableString
108
+ customData: NullableCustomData
109
+ privateCustomData: NullableCustomData
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: ...
83
111
 
84
112
  class SDKVariable_PB(_message.Message):
85
- __slots__ = ["_id", "boolValue", "doubleValue", "evalReason", "key", "stringValue", "type"]
113
+ __slots__ = ("_id", "type", "key", "boolValue", "doubleValue", "stringValue", "evalReason", "_feature", "eval")
114
+ _ID_FIELD_NUMBER: _ClassVar[int]
115
+ TYPE_FIELD_NUMBER: _ClassVar[int]
116
+ KEY_FIELD_NUMBER: _ClassVar[int]
86
117
  BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
87
118
  DOUBLEVALUE_FIELD_NUMBER: _ClassVar[int]
88
- EVALREASON_FIELD_NUMBER: _ClassVar[int]
89
- KEY_FIELD_NUMBER: _ClassVar[int]
90
119
  STRINGVALUE_FIELD_NUMBER: _ClassVar[int]
91
- TYPE_FIELD_NUMBER: _ClassVar[int]
92
- _ID_FIELD_NUMBER: _ClassVar[int]
120
+ EVALREASON_FIELD_NUMBER: _ClassVar[int]
121
+ _FEATURE_FIELD_NUMBER: _ClassVar[int]
122
+ EVAL_FIELD_NUMBER: _ClassVar[int]
93
123
  _id: str
124
+ type: VariableType_PB
125
+ key: str
94
126
  boolValue: bool
95
127
  doubleValue: float
96
- evalReason: NullableString
97
- key: str
98
128
  stringValue: str
99
- type: VariableType_PB
100
- 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]] = ...) -> None: ...
101
-
102
- class VariableForUserParams_PB(_message.Message):
103
- __slots__ = ["sdkKey", "shouldTrackEvent", "user", "variableKey", "variableType"]
104
- SDKKEY_FIELD_NUMBER: _ClassVar[int]
105
- SHOULDTRACKEVENT_FIELD_NUMBER: _ClassVar[int]
106
- USER_FIELD_NUMBER: _ClassVar[int]
107
- VARIABLEKEY_FIELD_NUMBER: _ClassVar[int]
108
- VARIABLETYPE_FIELD_NUMBER: _ClassVar[int]
109
- sdkKey: str
110
- shouldTrackEvent: bool
111
- user: DVCUser_PB
112
- variableKey: str
113
- variableType: VariableType_PB
114
- def __init__(self, sdkKey: _Optional[str] = ..., variableKey: _Optional[str] = ..., variableType: _Optional[_Union[VariableType_PB, str]] = ..., user: _Optional[_Union[DVCUser_PB, _Mapping]] = ..., shouldTrackEvent: bool = ...) -> None: ...
115
-
116
- class VariableType_PB(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
117
- __slots__ = []
129
+ evalReason: NullableString
130
+ _feature: NullableString
131
+ eval: EvalReason_PB
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: ...
118
133
 
119
- class CustomDataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
120
- __slots__ = []
134
+ class EvalReason_PB(_message.Message):
135
+ __slots__ = ("reason", "details", "target_id")
136
+ REASON_FIELD_NUMBER: _ClassVar[int]
137
+ DETAILS_FIELD_NUMBER: _ClassVar[int]
138
+ TARGET_ID_FIELD_NUMBER: _ClassVar[int]
139
+ reason: str
140
+ details: str
141
+ target_id: str
142
+ def __init__(self, reason: _Optional[str] = ..., details: _Optional[str] = ..., target_id: _Optional[str] = ...) -> None: ...
@@ -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:
@@ -1,55 +1,56 @@
1
- devcycle_python_sdk/VERSION.txt,sha256=GFGFHmpUHWFv2xS-M3W8EuUmBgBWdaseuXsFd5QicyE,7
1
+ devcycle_python_sdk/VERSION.txt,sha256=TuPL9BzJKZqNU-jwsDtquUrxVYO4LjhqEL1HqlHKDZU,7
2
2
  devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
3
- devcycle_python_sdk/bucketing-lib.release.wasm,sha256=FGGO3YnZHVs-ZY4XJM6FWpQwnPwHzFhBjQmrKPwWbtU,215612
4
- devcycle_python_sdk/cloud_client.py,sha256=pNKWMS70TzvVnvh0aspvffUtO6C_qQK8z9xC237QW7M,9064
3
+ devcycle_python_sdk/bucketing-lib.release.wasm,sha256=wpbXlOj_TVIO2OcOD781sW5ICmGsYe9pvxG6DwYw6FY,223167
4
+ devcycle_python_sdk/cloud_client.py,sha256=LMKB6gEuQNn5YePUPO0t1QIAUXot4OyshSUlUbuu07U,9413
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=DWzC92DhoDhpteqvTMpc-6IuaSmJYG7e_zgGy-MuDSw,11282
7
+ devcycle_python_sdk/local_client.py,sha256=obZlHBJ9MS79sntdpPb4JTwC29e58Q5PzzEtei3m9F0,11684
8
8
  devcycle_python_sdk/options.py,sha256=W3vNZfV6QNnQyaOuRf6Z5cK6ut6fWtlGqlUfhzWX0_c,5253
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
- devcycle_python_sdk/api/bucketing_client.py,sha256=-S0y9uAffippCTfjYgxI5j4j7YNfMxQJYUba1219nps,5111
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=_MAMCHviIS1n4GiU-CnnLBeEVX8BUFRqwNbA1IQi84U,16470
15
+ devcycle_python_sdk/api/local_bucketing.py,sha256=IXOP-680OmaO1XoX31YwY8FychAD5aqeyFFGe9SE_u4,15949
16
16
  devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  devcycle_python_sdk/managers/config_manager.py,sha256=AIGY9jXEhUzhEeaKIA1P5F2q6GaclrezAoGHgYU4dLQ,5634
18
18
  devcycle_python_sdk/managers/eval_hooks_manager.py,sha256=skOGK_hyZNpgkdeNXTTa-d1GVRITjoi8T6dO5I_Cqfk,2679
19
19
  devcycle_python_sdk/managers/event_queue_manager.py,sha256=uPy1vO6OAisZjUNAhWELE_g17alTiOcPv42z-WwkGck,8110
20
- devcycle_python_sdk/managers/sse_manager.py,sha256=PElIsmmjUvLDHABikLxUq6CVjIJDnmqkY3psPILCTS8,2590
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
23
  devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
24
24
  devcycle_python_sdk/models/eval_hook.py,sha256=_mDoEa8gkzMq-XpFRZrcXu_ldtpUzv5tlh-PghLpMMI,590
25
25
  devcycle_python_sdk/models/eval_hook_context.py,sha256=uBa8t0XxbcPBYAewNPFC2902qCjGhVoqUzZOZbGZ3n0,267
26
+ devcycle_python_sdk/models/eval_reason.py,sha256=nMXcbR1DM8eriCQ1NNnIRQKkhJSYAbY8_9GgBfj7P8k,943
26
27
  devcycle_python_sdk/models/event.py,sha256=Sj3eEopuPp8QwQl_Oz8E6Y9Ph6nLG0HxU7eR7xSbA7g,3722
27
28
  devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
28
29
  devcycle_python_sdk/models/platform_data.py,sha256=nyQsgI6pcTd1FL3Y2ACmpvTYb08R_UtWul25OWZstps,849
29
30
  devcycle_python_sdk/models/user.py,sha256=mhEk-mnKJRg_tydpBlAVhGDj9rDCacJ-jNwiGeSpZiE,7027
30
- devcycle_python_sdk/models/variable.py,sha256=qp00SyCNyfYiMlTwnDSDJ4ZetRF35JHTbYZQOBxFIqY,1711
31
+ devcycle_python_sdk/models/variable.py,sha256=NXubhYWXYRAUEh4kDe2Tu9T5QnZmQ3roYVPbP-u2H3s,3030
31
32
  devcycle_python_sdk/open_feature_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- devcycle_python_sdk/open_feature_provider/provider.py,sha256=nLqyW4n9_NrJqgaz6cUt3YHDoZQzorjTYPiyT4UjFdc,5255
33
+ devcycle_python_sdk/open_feature_provider/provider.py,sha256=l-psKU5m6jSBE53VwX3fBBas-TztekFDlfYTyTJQm6c,5558
33
34
  devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- devcycle_python_sdk/protobuf/utils.py,sha256=zi-kVx6yvgS2c04cUobLd2IDc_ZqKkWduay9YcpjoGc,4684
35
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=Zf4-8n67UnulC0Sz90aqLYsXCFEgywOqfiSHuXZomLk,4244
36
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=gjTD1KkBe5NaXixSq78eNTNbjdDjBQ0h5L4dUKKgDWQ,5659
35
+ devcycle_python_sdk/protobuf/utils.py,sha256=6cBL8GZ5nCZw6JoO7lAdgCj9wM1u0lp6QFQMnGE1fiM,5414
36
+ devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=yawFsQ7m-6OB7DxrjXo2FLLkHhiTv6lDnjubhjAgw_8,5198
37
+ devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=tfN0zBapUPA9WGqUG5lU5XXgH_BsHTt5mgXKGsyS4-I,6623
37
38
  devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
38
39
  devcycle_python_sdk/util/strings.py,sha256=gH0Xm3gIXNqn6hczKPk10Jn5_9knuD7OB5MrB7bqztw,252
39
40
  devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
40
- devcycle_python_server_sdk-3.11.4.dist-info/licenses/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
41
+ devcycle_python_server_sdk-3.12.0.dist-info/licenses/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
41
42
  example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
43
  example/cloud_client_example.py,sha256=YD8ZsTv3dPYfbFUKraRpRT0VsJGrPwjm8ZCuA5H54yI,2463
43
44
  example/local_bucketing_client_example.py,sha256=TaIfy3VnW9fXii9NPYs5UOuP4fLKruXMjnZApveqzl4,2796
44
45
  example/openfeature_example.py,sha256=kO6F-Xk20l6CPiMfIxSRreshEGmHMFHRg3zbeLXa3AI,2410
45
46
  test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- test/test_cloud_client.py,sha256=WpcHAZ6_T3XLWXTwXzxgUne7vFH8Ts5rseTau1M7SzQ,14224
47
- test/test_local_client.py,sha256=IMaFajfCD5UFW7Ge0P21DaxUD6GeZily-OT_Tx8vgkQ,15500
47
+ test/test_cloud_client.py,sha256=oqOWh4aa07ObJOJeEjXMon0O2jPW_lYZMVInbFR-uzI,14586
48
+ test/test_local_client.py,sha256=d0D0h2vqSiZItiqGXrlFXRRmuZG-TNg8MT84dY6w0Ks,16250
48
49
  test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  test/api/test_bucketing_client.py,sha256=M2mjACowLvbsIjXdP-w8-fwY02xqv1X1r_h9v-pXa7A,7739
50
51
  test/api/test_config_client.py,sha256=3BwMm4y-4NQcdW2DDGppoJLIF-ffgjlUJti2Qjw1D1M,5129
51
52
  test/api/test_event_client.py,sha256=v4Xoum-PdywfczrfMV1llM5vrIJoqHybzxPLVokh09g,3310
52
- test/api/test_local_bucketing.py,sha256=0mReZ4bsFUoJWPAIcQrWXDZ9UoC5mF9MuI-9BVB6qjM,15561
53
+ test/api/test_local_bucketing.py,sha256=0cbD8kiRYCVadvU-ldMES-sr7rV9Ld4H1q62HCx4AHU,15970
53
54
  test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  test/fixture/data.py,sha256=6ReLxWPMhCDbJ5Ex24_iR0PJNjKzPFV1I9j_v0FVbBU,1462
55
56
  test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -59,13 +60,13 @@ test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
60
  test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
60
61
  test/models/test_user.py,sha256=ZUbFUndYrnefyGl8rxZgG3HdHPZs4BosQWimkT7etJc,10540
61
62
  test/openfeature_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
- test/openfeature_test/test_provider.py,sha256=UPxAD5e7NvpIhmqNFCrTY_yrKrMyYxBc2xuMAgdSGvw,7653
63
- test/openfeature_test/test_provider_local_sdk.py,sha256=ad0QizKSS-lbqlIjiQvwtKHP7PCFfYam3OKHtvsFBkU,4426
63
+ test/openfeature_test/test_provider.py,sha256=hIzdqqBTulGRODeTcRJUP93v76WEolUC2QRe3DccF3U,10932
64
+ test/openfeature_test/test_provider_local_sdk.py,sha256=ExkA1LYLuvW-dD4Bw2BKggzDQ8GpnIp3WyNK4rUZgJQ,5696
64
65
  test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
66
  test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
66
67
  test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
67
68
  test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
68
- devcycle_python_server_sdk-3.11.4.dist-info/METADATA,sha256=pUSIwKnOlmwgTToZ6p5H8v7DcOUHxHfh_tNEB-e82A0,723
69
- devcycle_python_server_sdk-3.11.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- devcycle_python_server_sdk-3.11.4.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
71
- devcycle_python_server_sdk-3.11.4.dist-info/RECORD,,
69
+ devcycle_python_server_sdk-3.12.0.dist-info/METADATA,sha256=-bA66ayqIdC5UFrsLx0jZ9m4eNSHzcdTuFvA-sY-IQE,723
70
+ devcycle_python_server_sdk-3.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
71
+ devcycle_python_server_sdk-3.12.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
72
+ devcycle_python_server_sdk-3.12.0.dist-info/RECORD,,
@@ -10,6 +10,7 @@ from devcycle_python_sdk.models.bucketed_config import (
10
10
  Project,
11
11
  ProjectSettings,
12
12
  )
13
+ from devcycle_python_sdk.models.eval_reason import EvalReason
13
14
  from devcycle_python_sdk.models.feature import Feature
14
15
  from devcycle_python_sdk.models.variable import Variable
15
16
  from devcycle_python_sdk.models.platform_data import default_platform_data
@@ -155,6 +156,11 @@ class LocalBucketingTest(unittest.TestCase):
155
156
  evalReason=None,
156
157
  )
157
158
  }
159
+ expected_eval = EvalReason(
160
+ reason="TARGETING_MATCH",
161
+ details="All Users",
162
+ target_id="63125321d31c601f992288bc",
163
+ )
158
164
  expected_variables = {
159
165
  "a-cool-new-feature": Variable(
160
166
  _id="62fbf6566f1ba302829f9e34",
@@ -164,6 +170,7 @@ class LocalBucketingTest(unittest.TestCase):
164
170
  isDefaulted=None,
165
171
  defaultValue=None,
166
172
  evalReason=None,
173
+ eval=expected_eval,
167
174
  ),
168
175
  "string-var": Variable(
169
176
  _id="63125320a4719939fd57cb2b",
@@ -173,6 +180,7 @@ class LocalBucketingTest(unittest.TestCase):
173
180
  isDefaulted=None,
174
181
  defaultValue=None,
175
182
  evalReason=None,
183
+ eval=expected_eval,
176
184
  ),
177
185
  "json-var": Variable(
178
186
  _id="64372363125123fca69d3f7b",
@@ -186,6 +194,7 @@ class LocalBucketingTest(unittest.TestCase):
186
194
  isDefaulted=None,
187
195
  defaultValue=None,
188
196
  evalReason=None,
197
+ eval=expected_eval,
189
198
  ),
190
199
  "num-var": Variable(
191
200
  _id="65272363125123fca69d3a7d",
@@ -195,6 +204,7 @@ class LocalBucketingTest(unittest.TestCase):
195
204
  isDefaulted=None,
196
205
  defaultValue=None,
197
206
  evalReason=None,
207
+ eval=expected_eval,
198
208
  ),
199
209
  "float-var": Variable(
200
210
  _id="61200363125123fca69d3a7a",
@@ -204,6 +214,7 @@ class LocalBucketingTest(unittest.TestCase):
204
214
  isDefaulted=None,
205
215
  defaultValue=None,
206
216
  evalReason=None,
217
+ eval=expected_eval,
207
218
  ),
208
219
  }
209
220
 
@@ -12,6 +12,10 @@ from openfeature.exception import (
12
12
  )
13
13
 
14
14
  from devcycle_python_sdk.models.variable import Variable, TypeEnum
15
+ from devcycle_python_sdk.models.eval_reason import (
16
+ EvalReason,
17
+ DefaultReasonDetails,
18
+ )
15
19
 
16
20
  from devcycle_python_sdk.open_feature_provider.provider import (
17
21
  DevCycleProvider,
@@ -69,7 +73,9 @@ class DevCycleProviderTest(unittest.TestCase):
69
73
 
70
74
  def test_resolve_details_client_returns_default_variable(self):
71
75
  self.client.variable.return_value = Variable.create_default_variable(
72
- key="test-flag", default_value=False
76
+ key="test-flag",
77
+ default_value=False,
78
+ default_reason_detail=DefaultReasonDetails.USER_NOT_TARGETED,
73
79
  )
74
80
  context = EvaluationContext(targeting_key="user-1234")
75
81
  details = self.provider._resolve("test-flag", False, context)
@@ -77,6 +83,9 @@ class DevCycleProviderTest(unittest.TestCase):
77
83
  self.assertIsNotNone(details)
78
84
  self.assertEqual(details.value, False)
79
85
  self.assertEqual(details.reason, Reason.DEFAULT)
86
+ self.assertEqual(
87
+ details.flag_metadata["evalReasonDetails"], "User Not Targeted"
88
+ )
80
89
 
81
90
  def test_resolve_boolean_details(self):
82
91
  key = "test-flag"
@@ -90,6 +99,9 @@ class DevCycleProviderTest(unittest.TestCase):
90
99
  type=TypeEnum.BOOLEAN,
91
100
  isDefaulted=False,
92
101
  defaultValue=False,
102
+ eval=EvalReason(
103
+ reason="TARGETING_MATCH", details="All Users", target_id="targetId"
104
+ ),
93
105
  )
94
106
 
95
107
  context = EvaluationContext(targeting_key="user-1234")
@@ -98,6 +110,9 @@ class DevCycleProviderTest(unittest.TestCase):
98
110
  self.assertIsNotNone(details)
99
111
  self.assertEqual(details.value, variable_value)
100
112
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
113
+ self.assertEqual(details.reason, Reason.TARGETING_MATCH)
114
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
115
+ self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")
101
116
 
102
117
  def test_resolve_string_details(self):
103
118
  key = "test-flag"
@@ -111,6 +126,9 @@ class DevCycleProviderTest(unittest.TestCase):
111
126
  type=TypeEnum.STRING,
112
127
  isDefaulted=False,
113
128
  defaultValue=False,
129
+ eval=EvalReason(
130
+ reason="TARGETING_MATCH", details="All Users", target_id="targetId"
131
+ ),
114
132
  )
115
133
 
116
134
  context = EvaluationContext(targeting_key="user-1234")
@@ -120,6 +138,8 @@ class DevCycleProviderTest(unittest.TestCase):
120
138
  self.assertEqual(details.value, variable_value)
121
139
  self.assertIsInstance(details.value, str)
122
140
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
141
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
142
+ self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")
123
143
 
124
144
  def test_resolve_integer_details(self):
125
145
  key = "test-flag"
@@ -133,6 +153,9 @@ class DevCycleProviderTest(unittest.TestCase):
133
153
  type=TypeEnum.STRING,
134
154
  isDefaulted=False,
135
155
  defaultValue=False,
156
+ eval=EvalReason(
157
+ reason="TARGETING_MATCH", details="All Users", target_id="targetId"
158
+ ),
136
159
  )
137
160
 
138
161
  context = EvaluationContext(targeting_key="user-1234")
@@ -142,6 +165,8 @@ class DevCycleProviderTest(unittest.TestCase):
142
165
  self.assertIsInstance(details.value, int)
143
166
  self.assertEqual(details.value, variable_value)
144
167
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
168
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
169
+ self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")
145
170
 
146
171
  def test_resolve_float_details(self):
147
172
  key = "test-flag"
@@ -155,6 +180,7 @@ class DevCycleProviderTest(unittest.TestCase):
155
180
  type=TypeEnum.STRING,
156
181
  isDefaulted=False,
157
182
  defaultValue=False,
183
+ eval=EvalReason(reason="SPLIT", details="Rollout", target_id="targetId"),
158
184
  )
159
185
 
160
186
  context = EvaluationContext(targeting_key="user-1234")
@@ -163,7 +189,9 @@ class DevCycleProviderTest(unittest.TestCase):
163
189
  self.assertIsNotNone(details)
164
190
  self.assertIsInstance(details.value, float)
165
191
  self.assertEqual(details.value, variable_value)
166
- self.assertEqual(details.reason, Reason.TARGETING_MATCH)
192
+ self.assertEqual(details.reason, Reason.SPLIT)
193
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "Rollout")
194
+ self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")
167
195
 
168
196
  def test_resolve_object_details_verify_default_value(self):
169
197
  key = "test-flag"
@@ -204,6 +232,9 @@ class DevCycleProviderTest(unittest.TestCase):
204
232
  type=TypeEnum.STRING,
205
233
  isDefaulted=False,
206
234
  defaultValue=False,
235
+ eval=EvalReason(
236
+ reason="TARGETING_MATCH", details="Rollout", target_id="targetId"
237
+ ),
207
238
  )
208
239
 
209
240
  context = EvaluationContext(targeting_key="user-1234")
@@ -213,6 +244,52 @@ class DevCycleProviderTest(unittest.TestCase):
213
244
  self.assertIsInstance(details.value, dict)
214
245
  self.assertDictEqual(details.value, variable_value)
215
246
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
247
+ self.assertIsNotNone(details.flag_metadata)
248
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "Rollout")
249
+ self.assertEqual(details.flag_metadata["evalReasonTargetId"], "targetId")
250
+
251
+ def test_resolve_string_details_null_eval(self):
252
+ key = "test-flag"
253
+ variable_value = "some string"
254
+ default_value = "default string"
255
+
256
+ self.client.variable.return_value = Variable(
257
+ _id=None,
258
+ value=variable_value,
259
+ key=key,
260
+ type=TypeEnum.STRING,
261
+ isDefaulted=False,
262
+ defaultValue=False,
263
+ )
264
+
265
+ context = EvaluationContext(targeting_key="user-1234")
266
+ details = self.provider.resolve_string_details(key, default_value, context)
267
+
268
+ self.assertIsNotNone(details)
269
+ self.assertEqual(details.value, variable_value)
270
+ self.assertIsInstance(details.value, str)
271
+ self.assertEqual(details.reason, Reason.TARGETING_MATCH)
272
+
273
+ def test_default_string_details_null_eval(self):
274
+ key = "test-flag"
275
+ default_value = "default string"
276
+
277
+ self.client.variable.return_value = Variable(
278
+ _id=None,
279
+ value=default_value,
280
+ key=key,
281
+ type=TypeEnum.STRING,
282
+ isDefaulted=True,
283
+ defaultValue=False,
284
+ )
285
+
286
+ context = EvaluationContext(targeting_key="user-1234")
287
+ details = self.provider.resolve_string_details(key, default_value, context)
288
+
289
+ self.assertIsNotNone(details)
290
+ self.assertEqual(details.value, default_value)
291
+ self.assertIsInstance(details.value, str)
292
+ self.assertEqual(details.reason, Reason.DEFAULT)
216
293
 
217
294
 
218
295
  if __name__ == "__main__":
@@ -66,6 +66,11 @@ class DevCycleProviderWithLocalSDKTest(unittest.TestCase):
66
66
  self.assertIsNotNone(details)
67
67
  self.assertEqual(details.value, expected_value)
68
68
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
69
+ self.assertIsNotNone(details.flag_metadata)
70
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
71
+ self.assertEqual(
72
+ details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
73
+ )
69
74
 
70
75
  @responses.activate
71
76
  def test_resolve_integer_details(self):
@@ -82,6 +87,11 @@ class DevCycleProviderWithLocalSDKTest(unittest.TestCase):
82
87
  self.assertIsNotNone(details)
83
88
  self.assertEqual(details.value, expected_value)
84
89
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
90
+ self.assertIsNotNone(details.flag_metadata)
91
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
92
+ self.assertEqual(
93
+ details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
94
+ )
85
95
 
86
96
  @responses.activate
87
97
  def test_resolve_float_details(self):
@@ -98,6 +108,11 @@ class DevCycleProviderWithLocalSDKTest(unittest.TestCase):
98
108
  self.assertIsNotNone(details)
99
109
  self.assertEqual(details.value, expected_value)
100
110
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
111
+ self.assertIsNotNone(details.flag_metadata)
112
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
113
+ self.assertEqual(
114
+ details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
115
+ )
101
116
 
102
117
  @responses.activate
103
118
  def test_resolve_string_details(self):
@@ -114,6 +129,11 @@ class DevCycleProviderWithLocalSDKTest(unittest.TestCase):
114
129
  self.assertIsNotNone(details)
115
130
  self.assertEqual(details.value, expected_value)
116
131
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
132
+ self.assertIsNotNone(details.flag_metadata)
133
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
134
+ self.assertEqual(
135
+ details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
136
+ )
117
137
 
118
138
  @responses.activate
119
139
  def test_resolve_object_details(self):
@@ -134,3 +154,8 @@ class DevCycleProviderWithLocalSDKTest(unittest.TestCase):
134
154
  self.assertIsNotNone(details)
135
155
  self.assertEqual(details.value, expected_value)
136
156
  self.assertEqual(details.reason, Reason.TARGETING_MATCH)
157
+ self.assertIsNotNone(details.flag_metadata)
158
+ self.assertEqual(details.flag_metadata["evalReasonDetails"], "All Users")
159
+ self.assertEqual(
160
+ details.flag_metadata["evalReasonTargetId"], "63125321d31c601f992288bc"
161
+ )
test/test_cloud_client.py CHANGED
@@ -5,6 +5,7 @@ import uuid
5
5
  from time import time
6
6
  from unittest.mock import patch
7
7
 
8
+
8
9
  from devcycle_python_sdk import DevCycleCloudClient, DevCycleCloudOptions
9
10
  from devcycle_python_sdk.models.eval_hook import EvalHook
10
11
  from devcycle_python_sdk.models.user import DevCycleUser
@@ -97,6 +98,8 @@ class DevCycleCloudClientTest(unittest.TestCase):
97
98
  self.assertIsNotNone(result)
98
99
  self.assertEqual(result.value, "default_value")
99
100
  self.assertTrue(result.isDefaulted)
101
+ self.assertEqual(result.eval.reason, "DEFAULT")
102
+ self.assertEqual(result.eval.details, "Missing Variable")
100
103
 
101
104
  # other exception - return default
102
105
  mock_variable_call.reset_mock()
@@ -105,6 +108,8 @@ class DevCycleCloudClientTest(unittest.TestCase):
105
108
  self.assertIsNotNone(result)
106
109
  self.assertEqual(result.value, "default_value")
107
110
  self.assertTrue(result.isDefaulted)
111
+ self.assertEqual(result.eval.reason, "DEFAULT")
112
+ self.assertEqual(result.eval.details, "Error")
108
113
 
109
114
  @patch("devcycle_python_sdk.api.bucketing_client.BucketingAPIClient.variable")
110
115
  def test_variable_type_mismatch(self, mock_variable_call):
@@ -116,6 +121,8 @@ class DevCycleCloudClientTest(unittest.TestCase):
116
121
  self.assertIsNotNone(result)
117
122
  self.assertEqual(result.value, "default_value")
118
123
  self.assertTrue(result.isDefaulted)
124
+ self.assertEqual(result.eval.reason, "DEFAULT")
125
+ self.assertEqual(result.eval.details, "Variable Type Mismatch")
119
126
 
120
127
  @patch("devcycle_python_sdk.api.bucketing_client.BucketingAPIClient.variable")
121
128
  def test_variable_value_defaults(self, mock_variable_call):
test/test_local_client.py CHANGED
@@ -10,6 +10,7 @@ from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
10
10
  from devcycle_python_sdk.local_client import _validate_user, _validate_sdk_key
11
11
  from devcycle_python_sdk.exceptions import MalformedConfigError
12
12
  from devcycle_python_sdk.models.eval_hook import EvalHook
13
+ from devcycle_python_sdk.models.eval_reason import EvalReason
13
14
  from devcycle_python_sdk.models.event import DevCycleEvent
14
15
  from devcycle_python_sdk.models.feature import Feature
15
16
  from devcycle_python_sdk.api.local_bucketing import LocalBucketing
@@ -208,6 +209,8 @@ class DevCycleLocalClientTest(unittest.TestCase):
208
209
  self.assertEqual(result.defaultValue, test_value)
209
210
  self.assertEqual(result.value, test_value)
210
211
  self.assertEqual(result.type, value_type)
212
+ self.assertEqual(result.eval.reason, "DEFAULT")
213
+ self.assertEqual(result.eval.details, "User Not Targeted")
211
214
 
212
215
  @responses.activate
213
216
  def test_variable_with_bucketing(self):
@@ -241,6 +244,9 @@ class DevCycleLocalClientTest(unittest.TestCase):
241
244
  self.assertDictEqual(result.value, expected, msg="Test key: " + key)
242
245
  else:
243
246
  self.assertEqual(result.value, expected, msg="Test key: " + key)
247
+ self.assertEqual(result.eval.reason, "TARGETING_MATCH")
248
+ self.assertEqual(result.eval.details, "All Users")
249
+ self.assertEqual(result.eval.target_id, "63125321d31c601f992288bc")
244
250
 
245
251
  @responses.activate
246
252
  def test_variable_with_events(self):
@@ -294,7 +300,11 @@ class DevCycleLocalClientTest(unittest.TestCase):
294
300
  user = DevCycleUser(user_id="1234")
295
301
 
296
302
  result = self.client.all_variables(user)
297
-
303
+ expected_eval = EvalReason(
304
+ reason="TARGETING_MATCH",
305
+ details="All Users",
306
+ target_id="63125321d31c601f992288bc",
307
+ )
298
308
  expected_variables = {
299
309
  "a-cool-new-feature": Variable(
300
310
  _id="62fbf6566f1ba302829f9e34",
@@ -304,6 +314,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
304
314
  isDefaulted=None,
305
315
  defaultValue=None,
306
316
  evalReason=None,
317
+ eval=expected_eval,
307
318
  ),
308
319
  "string-var": Variable(
309
320
  _id="63125320a4719939fd57cb2b",
@@ -313,6 +324,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
313
324
  isDefaulted=None,
314
325
  defaultValue=None,
315
326
  evalReason=None,
327
+ eval=expected_eval,
316
328
  ),
317
329
  "json-var": Variable(
318
330
  _id="64372363125123fca69d3f7b",
@@ -326,6 +338,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
326
338
  isDefaulted=None,
327
339
  defaultValue=None,
328
340
  evalReason=None,
341
+ eval=expected_eval,
329
342
  ),
330
343
  "num-var": Variable(
331
344
  _id="65272363125123fca69d3a7d",
@@ -335,6 +348,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
335
348
  isDefaulted=None,
336
349
  defaultValue=None,
337
350
  evalReason=None,
351
+ eval=expected_eval,
338
352
  ),
339
353
  "float-var": Variable(
340
354
  _id="61200363125123fca69d3a7a",
@@ -344,6 +358,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
344
358
  isDefaulted=None,
345
359
  defaultValue=None,
346
360
  evalReason=None,
361
+ eval=expected_eval,
347
362
  ),
348
363
  }
349
364
  self.assertEqual(result, expected_variables)