devcycle-python-server-sdk 3.12.0__py3-none-any.whl → 3.13.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devcycle_python_sdk/VERSION.txt +1 -1
- devcycle_python_sdk/api/local_bucketing.py +19 -4
- devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
- devcycle_python_sdk/cloud_client.py +3 -3
- devcycle_python_sdk/local_client.py +13 -5
- devcycle_python_sdk/managers/config_manager.py +4 -0
- devcycle_python_sdk/managers/eval_hooks_manager.py +15 -4
- devcycle_python_sdk/managers/event_queue_manager.py +0 -1
- devcycle_python_sdk/models/config_metadata.py +40 -0
- devcycle_python_sdk/models/environment_metadata.py +26 -0
- devcycle_python_sdk/models/eval_hook.py +5 -2
- devcycle_python_sdk/models/eval_hook_context.py +23 -5
- devcycle_python_sdk/models/eval_reason.py +0 -1
- devcycle_python_sdk/models/project_metadata.py +24 -0
- devcycle_python_sdk/models/user.py +3 -3
- devcycle_python_sdk/models/variable_metadata.py +22 -0
- devcycle_python_sdk/open_feature_provider/provider.py +7 -5
- devcycle_python_sdk/options.py +1 -1
- devcycle_python_sdk/protobuf/utils.py +9 -3
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +34 -42
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +11 -11
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/METADATA +2 -2
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/RECORD +32 -28
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/licenses/LICENSE +1 -1
- example/openfeature_example.py +175 -14
- test/api/test_local_bucketing.py +6 -3
- test/models/test_user.py +0 -1
- test/openfeature_test/test_provider.py +0 -1
- test/test_cloud_client.py +41 -6
- test/test_local_client.py +53 -4
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/WHEEL +0 -0
- {devcycle_python_server_sdk-3.12.0.dist-info → devcycle_python_server_sdk-3.13.1.dist-info}/top_level.txt +0 -0
devcycle_python_sdk/VERSION.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.13.1
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from threading import Lock
|
|
8
|
-
from typing import Any, cast, Optional, List
|
|
8
|
+
from typing import Any, cast, Optional, List, Tuple
|
|
9
9
|
|
|
10
10
|
import wasmtime
|
|
11
11
|
from wasmtime import (
|
|
@@ -28,6 +28,7 @@ from devcycle_python_sdk.models.bucketed_config import BucketedConfig
|
|
|
28
28
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
29
29
|
from devcycle_python_sdk.models.variable import Variable, determine_variable_type
|
|
30
30
|
from devcycle_python_sdk.models.event import FlushPayload
|
|
31
|
+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
|
|
31
32
|
|
|
32
33
|
logger = logging.getLogger(__name__)
|
|
33
34
|
|
|
@@ -140,6 +141,7 @@ class LocalBucketing:
|
|
|
140
141
|
"generateBucketedConfigForUserUTF8"
|
|
141
142
|
)
|
|
142
143
|
self.VariableForUserProtobuf = self._get_export("variableForUser_PB")
|
|
144
|
+
self.getConfigMetadata = self._get_export("getConfigMetadata")
|
|
143
145
|
|
|
144
146
|
# Extract variable type enum values from WASM
|
|
145
147
|
self.variable_type_map = {
|
|
@@ -298,7 +300,7 @@ class LocalBucketing:
|
|
|
298
300
|
|
|
299
301
|
def get_variable_for_user_protobuf(
|
|
300
302
|
self, user: DevCycleUser, key: str, default_value: Any
|
|
301
|
-
) -> Optional[Variable]:
|
|
303
|
+
) -> Tuple[Optional[Variable], Optional[str]]:
|
|
302
304
|
var_type = determine_variable_type(default_value)
|
|
303
305
|
pb_variable_type = pb_utils.convert_type_enum_to_variable_type(var_type)
|
|
304
306
|
|
|
@@ -317,13 +319,18 @@ class LocalBucketing:
|
|
|
317
319
|
variable_addr = self.VariableForUserProtobuf(self.wasm_store, params_addr)
|
|
318
320
|
|
|
319
321
|
if variable_addr == 0:
|
|
320
|
-
return None
|
|
322
|
+
return None, None
|
|
321
323
|
else:
|
|
322
324
|
var_bytes = self._read_assembly_script_byte_array(variable_addr)
|
|
323
325
|
sdk_variable = pb2.SDKVariable_PB()
|
|
324
326
|
sdk_variable.ParseFromString(var_bytes)
|
|
327
|
+
feature_id = (
|
|
328
|
+
sdk_variable._feature.value
|
|
329
|
+
if sdk_variable._feature.value is not None
|
|
330
|
+
else None
|
|
331
|
+
)
|
|
325
332
|
|
|
326
|
-
return pb_utils.create_variable(sdk_variable, default_value)
|
|
333
|
+
return pb_utils.create_variable(sdk_variable, default_value), feature_id
|
|
327
334
|
|
|
328
335
|
def generate_bucketed_config(self, user: DevCycleUser) -> BucketedConfig:
|
|
329
336
|
user_json = json.dumps(user.to_json())
|
|
@@ -357,6 +364,14 @@ class LocalBucketing:
|
|
|
357
364
|
config_addr = self._new_assembly_script_byte_array(data)
|
|
358
365
|
self.setConfigDataUTF8(self.wasm_store, self.sdk_key_addr, config_addr)
|
|
359
366
|
|
|
367
|
+
def get_config_metadata(self) -> Optional[ConfigMetadata]:
|
|
368
|
+
with self.wasm_lock:
|
|
369
|
+
config_addr = self.getConfigMetadata(self.wasm_store, self.sdk_key_addr)
|
|
370
|
+
config_bytes = self._read_assembly_script_string(config_addr)
|
|
371
|
+
config_data = json.loads(config_bytes.encode("utf-8"))
|
|
372
|
+
|
|
373
|
+
return ConfigMetadata.from_json(config_data)
|
|
374
|
+
|
|
360
375
|
def set_platform_data(self, platform_json: str) -> None:
|
|
361
376
|
with self.wasm_lock:
|
|
362
377
|
data = platform_json.encode("utf-8")
|
|
Binary file
|
|
@@ -115,7 +115,7 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
115
115
|
before_hook_error = e
|
|
116
116
|
variable = self.bucketing_api.variable(key, context.user)
|
|
117
117
|
if before_hook_error is None:
|
|
118
|
-
self.eval_hooks_manager.run_after(context, variable)
|
|
118
|
+
self.eval_hooks_manager.run_after(context, variable, None)
|
|
119
119
|
else:
|
|
120
120
|
raise before_hook_error
|
|
121
121
|
except CloudClientUnauthorizedError as e:
|
|
@@ -126,7 +126,7 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
126
126
|
return Variable.create_default_variable(
|
|
127
127
|
key=key,
|
|
128
128
|
default_value=default_value,
|
|
129
|
-
default_reason_detail=DefaultReasonDetails.
|
|
129
|
+
default_reason_detail=DefaultReasonDetails.ERROR,
|
|
130
130
|
)
|
|
131
131
|
except BeforeHookError as e:
|
|
132
132
|
self.eval_hooks_manager.run_error(context, e)
|
|
@@ -140,7 +140,7 @@ class DevCycleCloudClient(AbstractDevCycleClient):
|
|
|
140
140
|
default_reason_detail=DefaultReasonDetails.ERROR,
|
|
141
141
|
)
|
|
142
142
|
finally:
|
|
143
|
-
self.eval_hooks_manager.run_finally(context, variable)
|
|
143
|
+
self.eval_hooks_manager.run_finally(context, variable, None)
|
|
144
144
|
|
|
145
145
|
variable.defaultValue = default_value
|
|
146
146
|
|
|
@@ -26,6 +26,7 @@ from devcycle_python_sdk.models.feature import Feature
|
|
|
26
26
|
from devcycle_python_sdk.models.platform_data import default_platform_data
|
|
27
27
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
28
28
|
from devcycle_python_sdk.models.variable import Variable
|
|
29
|
+
from devcycle_python_sdk.models.variable_metadata import VariableMetadata
|
|
29
30
|
from devcycle_python_sdk.open_feature_provider.provider import DevCycleProvider
|
|
30
31
|
from openfeature.provider import AbstractProvider
|
|
31
32
|
|
|
@@ -150,7 +151,10 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
150
151
|
key, default_value, DefaultReasonDetails.MISSING_CONFIG
|
|
151
152
|
)
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
config_metadata = self.local_bucketing.get_config_metadata()
|
|
155
|
+
variable_metadata = None
|
|
156
|
+
|
|
157
|
+
context = HookContext(key, user, default_value, config_metadata)
|
|
154
158
|
variable = Variable.create_default_variable(
|
|
155
159
|
key=key, default_value=default_value
|
|
156
160
|
)
|
|
@@ -163,9 +167,13 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
163
167
|
context = changed_context
|
|
164
168
|
except BeforeHookError as e:
|
|
165
169
|
before_hook_error = e
|
|
166
|
-
bucketed_variable =
|
|
167
|
-
|
|
170
|
+
bucketed_variable, feature_id = (
|
|
171
|
+
self.local_bucketing.get_variable_for_user_protobuf(
|
|
172
|
+
user, key, default_value
|
|
173
|
+
)
|
|
168
174
|
)
|
|
175
|
+
if feature_id is not None:
|
|
176
|
+
variable_metadata = VariableMetadata(feature_id=feature_id)
|
|
169
177
|
if bucketed_variable is not None:
|
|
170
178
|
variable = bucketed_variable
|
|
171
179
|
else:
|
|
@@ -175,7 +183,7 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
175
183
|
)
|
|
176
184
|
|
|
177
185
|
if before_hook_error is None:
|
|
178
|
-
self.eval_hooks_manager.run_after(context, variable)
|
|
186
|
+
self.eval_hooks_manager.run_after(context, variable, variable_metadata)
|
|
179
187
|
else:
|
|
180
188
|
raise before_hook_error
|
|
181
189
|
except Exception as e:
|
|
@@ -192,7 +200,7 @@ class DevCycleLocalClient(AbstractDevCycleClient):
|
|
|
192
200
|
|
|
193
201
|
return variable
|
|
194
202
|
finally:
|
|
195
|
-
self.eval_hooks_manager.run_finally(context, variable)
|
|
203
|
+
self.eval_hooks_manager.run_finally(context, variable, variable_metadata)
|
|
196
204
|
return variable
|
|
197
205
|
|
|
198
206
|
def _generate_bucketed_config(self, user: DevCycleUser) -> BucketedConfig:
|
|
@@ -16,6 +16,7 @@ from devcycle_python_sdk.exceptions import (
|
|
|
16
16
|
from wsgiref.handlers import format_date_time
|
|
17
17
|
from devcycle_python_sdk.options import DevCycleLocalOptions
|
|
18
18
|
from devcycle_python_sdk.managers.sse_manager import SSEManager
|
|
19
|
+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
@@ -108,6 +109,9 @@ class EnvironmentConfigManager(threading.Thread):
|
|
|
108
109
|
)
|
|
109
110
|
self._polling_enabled = False
|
|
110
111
|
|
|
112
|
+
def get_config_metadata(self) -> Optional[ConfigMetadata]:
|
|
113
|
+
return self._local_bucketing.get_config_metadata()
|
|
114
|
+
|
|
111
115
|
def run(self):
|
|
112
116
|
while self._polling_enabled:
|
|
113
117
|
try:
|
|
@@ -3,6 +3,7 @@ from typing import List, Optional
|
|
|
3
3
|
from devcycle_python_sdk.models.eval_hook import EvalHook
|
|
4
4
|
from devcycle_python_sdk.models.eval_hook_context import HookContext
|
|
5
5
|
from devcycle_python_sdk.models.variable import Variable
|
|
6
|
+
from devcycle_python_sdk.models.variable_metadata import VariableMetadata
|
|
6
7
|
from devcycle_python_sdk.options import logger
|
|
7
8
|
|
|
8
9
|
|
|
@@ -48,19 +49,29 @@ class EvalHooksManager:
|
|
|
48
49
|
raise BeforeHookError(f"Before hook failed: {e}", e)
|
|
49
50
|
return modified_context
|
|
50
51
|
|
|
51
|
-
def run_after(
|
|
52
|
+
def run_after(
|
|
53
|
+
self,
|
|
54
|
+
context: HookContext,
|
|
55
|
+
variable: Variable,
|
|
56
|
+
variable_metadata: Optional[VariableMetadata],
|
|
57
|
+
) -> None:
|
|
52
58
|
"""Run after hooks with the evaluation result"""
|
|
53
59
|
for hook in self.hooks:
|
|
54
60
|
try:
|
|
55
|
-
hook.after(context, variable)
|
|
61
|
+
hook.after(context, variable, variable_metadata)
|
|
56
62
|
except Exception as e:
|
|
57
63
|
raise AfterHookError(f"After hook failed: {e}", e)
|
|
58
64
|
|
|
59
|
-
def run_finally(
|
|
65
|
+
def run_finally(
|
|
66
|
+
self,
|
|
67
|
+
context: HookContext,
|
|
68
|
+
variable: Optional[Variable],
|
|
69
|
+
variable_metadata: Optional[VariableMetadata],
|
|
70
|
+
) -> None:
|
|
60
71
|
"""Run finally hooks after evaluation completes"""
|
|
61
72
|
for hook in self.hooks:
|
|
62
73
|
try:
|
|
63
|
-
hook.on_finally(context, variable)
|
|
74
|
+
hook.on_finally(context, variable, variable_metadata)
|
|
64
75
|
except Exception as e:
|
|
65
76
|
logger.error(f"Error running finally hook: {e}")
|
|
66
77
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from devcycle_python_sdk.models.environment_metadata import EnvironmentMetadata
|
|
2
|
+
from devcycle_python_sdk.models.project_metadata import ProjectMetadata
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ConfigMetadata:
|
|
9
|
+
project: ProjectMetadata
|
|
10
|
+
environment: EnvironmentMetadata
|
|
11
|
+
|
|
12
|
+
def to_json(self):
|
|
13
|
+
result = {}
|
|
14
|
+
for field_name in self.__dataclass_fields__:
|
|
15
|
+
value = getattr(self, field_name)
|
|
16
|
+
if value is not None:
|
|
17
|
+
if field_name == "project" and isinstance(value, ProjectMetadata):
|
|
18
|
+
result[field_name] = value.to_json()
|
|
19
|
+
elif field_name == "environment" and isinstance(
|
|
20
|
+
value, EnvironmentMetadata
|
|
21
|
+
):
|
|
22
|
+
result[field_name] = value.to_json()
|
|
23
|
+
else:
|
|
24
|
+
result[field_name] = value
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def from_json(json_obj: Optional[Dict[str, Any]]) -> Optional["ConfigMetadata"]:
|
|
29
|
+
if json_obj is None:
|
|
30
|
+
return None
|
|
31
|
+
project = ProjectMetadata.from_json(json_obj.get("project"))
|
|
32
|
+
environment = EnvironmentMetadata.from_json(json_obj.get("environment"))
|
|
33
|
+
|
|
34
|
+
if project is None or environment is None:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
return ConfigMetadata(
|
|
38
|
+
project=project,
|
|
39
|
+
environment=environment,
|
|
40
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class EnvironmentMetadata:
|
|
7
|
+
id: str
|
|
8
|
+
key: str
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def from_json(
|
|
12
|
+
json_obj: Optional[Dict[str, Any]],
|
|
13
|
+
) -> Optional["EnvironmentMetadata"]:
|
|
14
|
+
if json_obj is None:
|
|
15
|
+
return None
|
|
16
|
+
return EnvironmentMetadata(
|
|
17
|
+
id=json_obj["id"],
|
|
18
|
+
key=json_obj["key"],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def to_json(self):
|
|
22
|
+
result = {}
|
|
23
|
+
for field_name in self.__dataclass_fields__:
|
|
24
|
+
value = getattr(self, field_name)
|
|
25
|
+
result[field_name] = value
|
|
26
|
+
return result
|
|
@@ -2,14 +2,17 @@ from typing import Callable, Optional
|
|
|
2
2
|
|
|
3
3
|
from devcycle_python_sdk.models.eval_hook_context import HookContext
|
|
4
4
|
from devcycle_python_sdk.models.variable import Variable
|
|
5
|
+
from devcycle_python_sdk.models.variable_metadata import VariableMetadata
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class EvalHook:
|
|
8
9
|
def __init__(
|
|
9
10
|
self,
|
|
10
11
|
before: Callable[[HookContext], Optional[HookContext]],
|
|
11
|
-
after: Callable[[HookContext, Variable], None],
|
|
12
|
-
on_finally: Callable[
|
|
12
|
+
after: Callable[[HookContext, Variable, Optional[VariableMetadata]], None],
|
|
13
|
+
on_finally: Callable[
|
|
14
|
+
[HookContext, Optional[Variable], Optional[VariableMetadata]], None
|
|
15
|
+
],
|
|
13
16
|
error: Callable[[HookContext, Exception], None],
|
|
14
17
|
):
|
|
15
18
|
self.before = before
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
2
3
|
|
|
3
4
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
5
|
+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
@dataclass
|
|
6
9
|
class HookContext:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
key: str
|
|
11
|
+
user: DevCycleUser
|
|
12
|
+
default_value: Any
|
|
13
|
+
config_metadata: Optional[ConfigMetadata] = None
|
|
14
|
+
|
|
15
|
+
def to_json(self):
|
|
16
|
+
result = {}
|
|
17
|
+
for field_name in self.__dataclass_fields__:
|
|
18
|
+
value = getattr(self, field_name)
|
|
19
|
+
if value is not None:
|
|
20
|
+
if field_name == "config_metadata" and isinstance(
|
|
21
|
+
value, ConfigMetadata
|
|
22
|
+
):
|
|
23
|
+
result[field_name] = value.to_json()
|
|
24
|
+
elif field_name == "user" and isinstance(value, DevCycleUser):
|
|
25
|
+
result[field_name] = value.to_json()
|
|
26
|
+
else:
|
|
27
|
+
result[field_name] = value
|
|
28
|
+
return result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class ProjectMetadata:
|
|
7
|
+
id: str
|
|
8
|
+
key: str
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def from_json(json_obj: Optional[Dict[str, Any]]) -> Optional["ProjectMetadata"]:
|
|
12
|
+
if json_obj is None:
|
|
13
|
+
return None
|
|
14
|
+
return ProjectMetadata(
|
|
15
|
+
id=json_obj["id"],
|
|
16
|
+
key=json_obj["key"],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def to_json(self):
|
|
20
|
+
result = {}
|
|
21
|
+
for field_name in self.__dataclass_fields__:
|
|
22
|
+
value = getattr(self, field_name)
|
|
23
|
+
result[field_name] = value
|
|
24
|
+
return result
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ruff: noqa: N815
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import Dict, Optional, Any
|
|
4
|
+
from typing import Dict, Optional, Any, cast
|
|
5
5
|
from openfeature.evaluation_context import EvaluationContext
|
|
6
6
|
from openfeature.exception import TargetingKeyMissingError, InvalidContextError
|
|
7
7
|
|
|
@@ -114,10 +114,10 @@ class DevCycleUser:
|
|
|
114
114
|
user_id = context.targeting_key
|
|
115
115
|
user_id_source = "targeting_key"
|
|
116
116
|
elif context.attributes and "user_id" in context.attributes.keys():
|
|
117
|
-
user_id = context.attributes["user_id"]
|
|
117
|
+
user_id = cast(str, context.attributes["user_id"])
|
|
118
118
|
user_id_source = "user_id"
|
|
119
119
|
elif context.attributes and "userId" in context.attributes.keys():
|
|
120
|
-
user_id = context.attributes["userId"]
|
|
120
|
+
user_id = cast(str, context.attributes["userId"])
|
|
121
121
|
user_id_source = "userId"
|
|
122
122
|
|
|
123
123
|
if not user_id:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class VariableMetadata:
|
|
7
|
+
feature_id: str
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def from_json(json_obj: Optional[Dict[str, Any]]) -> Optional["VariableMetadata"]:
|
|
11
|
+
if json_obj is None:
|
|
12
|
+
return None
|
|
13
|
+
return VariableMetadata(
|
|
14
|
+
feature_id=json_obj["feature_id"],
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def to_json(self):
|
|
18
|
+
result = {}
|
|
19
|
+
for field_name in self.__dataclass_fields__:
|
|
20
|
+
value = getattr(self, field_name)
|
|
21
|
+
result[field_name] = value
|
|
22
|
+
return result
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
|
|
4
|
-
from typing import Any, Optional, Union, List
|
|
4
|
+
from typing import Any, Optional, Union, List, Mapping, Sequence
|
|
5
5
|
|
|
6
6
|
from devcycle_python_sdk import AbstractDevCycleClient
|
|
7
7
|
from devcycle_python_sdk.models.user import DevCycleUser
|
|
@@ -9,7 +9,7 @@ from devcycle_python_sdk.models.user import DevCycleUser
|
|
|
9
9
|
from openfeature.provider import AbstractProvider
|
|
10
10
|
from openfeature.provider.metadata import Metadata
|
|
11
11
|
from openfeature.evaluation_context import EvaluationContext
|
|
12
|
-
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
12
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, Reason, FlagValueType
|
|
13
13
|
from openfeature.exception import (
|
|
14
14
|
ErrorCode,
|
|
15
15
|
InvalidContextError,
|
|
@@ -138,10 +138,12 @@ class DevCycleProvider(AbstractProvider):
|
|
|
138
138
|
def resolve_object_details(
|
|
139
139
|
self,
|
|
140
140
|
flag_key: str,
|
|
141
|
-
default_value: Union[
|
|
141
|
+
default_value: Union[Mapping[str, FlagValueType], Sequence[FlagValueType]],
|
|
142
142
|
evaluation_context: Optional[EvaluationContext] = None,
|
|
143
|
-
) -> FlagResolutionDetails[
|
|
144
|
-
|
|
143
|
+
) -> FlagResolutionDetails[
|
|
144
|
+
Union[Mapping[str, FlagValueType], Sequence[FlagValueType]]
|
|
145
|
+
]:
|
|
146
|
+
if not isinstance(default_value, Mapping):
|
|
145
147
|
raise TypeMismatchError("Default value must be a flat dictionary")
|
|
146
148
|
|
|
147
149
|
if default_value:
|
devcycle_python_sdk/options.py
CHANGED
|
@@ -90,7 +90,7 @@ class DevCycleLocalOptions:
|
|
|
90
90
|
|
|
91
91
|
if self.event_request_chunk_size > self.max_event_queue_size:
|
|
92
92
|
logger.warning(
|
|
93
|
-
f"DevCycle: event_request_chunk_size: {self.event_request_chunk_size} must be smaller than max_event_queue_size: {
|
|
93
|
+
f"DevCycle: event_request_chunk_size: {self.event_request_chunk_size} must be smaller than max_event_queue_size: {self.max_event_queue_size}"
|
|
94
94
|
)
|
|
95
95
|
self.event_request_chunk_size = 100
|
|
96
96
|
|
|
@@ -34,11 +34,17 @@ def create_nullable_custom_data(val: Optional[dict]) -> pb2.NullableCustomData:
|
|
|
34
34
|
if value is None:
|
|
35
35
|
values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Null) # type: ignore
|
|
36
36
|
elif isinstance(value, bool):
|
|
37
|
-
values[key] = pb2.CustomDataValue(
|
|
37
|
+
values[key] = pb2.CustomDataValue(
|
|
38
|
+
type=pb2.CustomDataType.Bool, boolValue=value
|
|
39
|
+
) # type: ignore
|
|
38
40
|
elif isinstance(value, str):
|
|
39
|
-
values[key] = pb2.CustomDataValue(
|
|
41
|
+
values[key] = pb2.CustomDataValue(
|
|
42
|
+
type=pb2.CustomDataType.Str, stringValue=value
|
|
43
|
+
) # type: ignore
|
|
40
44
|
elif isinstance(value, (int, float)):
|
|
41
|
-
values[key] = pb2.CustomDataValue(
|
|
45
|
+
values[key] = pb2.CustomDataValue(
|
|
46
|
+
type=pb2.CustomDataType.Num, doubleValue=value
|
|
47
|
+
) # type: ignore
|
|
42
48
|
else:
|
|
43
49
|
logger.warning(
|
|
44
50
|
f"Custom Data contains data type that can't be written, will be ignored. Key: {key}, Type: {str(type(value))}"
|
|
@@ -1,59 +1,51 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
3
|
# source: variableForUserParams.proto
|
|
5
|
-
# Protobuf Python Version: 5.29.3
|
|
6
4
|
"""Generated protocol buffer code."""
|
|
5
|
+
|
|
7
6
|
from google.protobuf import descriptor as _descriptor
|
|
8
7
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
-
from google.protobuf import runtime_version as _runtime_version
|
|
10
8
|
from google.protobuf import symbol_database as _symbol_database
|
|
11
9
|
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
10
|
# @@protoc_insertion_point(imports)
|
|
21
11
|
|
|
22
12
|
_sym_db = _symbol_database.Default()
|
|
23
13
|
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
|
16
|
+
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'
|
|
17
|
+
)
|
|
28
18
|
|
|
29
19
|
_globals = globals()
|
|
30
20
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
-
_builder.BuildTopDescriptorsAndMessages(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
21
|
+
_builder.BuildTopDescriptorsAndMessages(
|
|
22
|
+
DESCRIPTOR, "variableForUserParams_pb2", _globals
|
|
23
|
+
)
|
|
24
|
+
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
25
|
+
DESCRIPTOR._options = None
|
|
26
|
+
DESCRIPTOR._serialized_options = b'\n&com.devcycle.sdk.server.local.protobufP\001Z\007./proto\252\002"DevCycle.SDK.Server.Local.Protobuf'
|
|
27
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._options = None
|
|
28
|
+
_NULLABLECUSTOMDATA_VALUEENTRY._serialized_options = b"8\001"
|
|
29
|
+
_globals["_VARIABLETYPE_PB"]._serialized_start = 1221
|
|
30
|
+
_globals["_VARIABLETYPE_PB"]._serialized_end = 1285
|
|
31
|
+
_globals["_CUSTOMDATATYPE"]._serialized_start = 1287
|
|
32
|
+
_globals["_CUSTOMDATATYPE"]._serialized_end = 1341
|
|
33
|
+
_globals["_NULLABLESTRING"]._serialized_start = 31
|
|
34
|
+
_globals["_NULLABLESTRING"]._serialized_end = 78
|
|
35
|
+
_globals["_NULLABLEDOUBLE"]._serialized_start = 80
|
|
36
|
+
_globals["_NULLABLEDOUBLE"]._serialized_end = 127
|
|
37
|
+
_globals["_CUSTOMDATAVALUE"]._serialized_start = 129
|
|
38
|
+
_globals["_CUSTOMDATAVALUE"]._serialized_end = 238
|
|
39
|
+
_globals["_NULLABLECUSTOMDATA"]._serialized_start = 241
|
|
40
|
+
_globals["_NULLABLECUSTOMDATA"]._serialized_end = 388
|
|
41
|
+
_globals["_NULLABLECUSTOMDATA_VALUEENTRY"]._serialized_start = 326
|
|
42
|
+
_globals["_NULLABLECUSTOMDATA_VALUEENTRY"]._serialized_end = 388
|
|
43
|
+
_globals["_VARIABLEFORUSERPARAMS_PB"]._serialized_start = 391
|
|
44
|
+
_globals["_VARIABLEFORUSERPARAMS_PB"]._serialized_end = 547
|
|
45
|
+
_globals["_DVCUSER_PB"]._serialized_start = 550
|
|
46
|
+
_globals["_DVCUSER_PB"]._serialized_end = 910
|
|
47
|
+
_globals["_SDKVARIABLE_PB"]._serialized_start = 913
|
|
48
|
+
_globals["_SDKVARIABLE_PB"]._serialized_end = 1150
|
|
49
|
+
_globals["_EVALREASON_PB"]._serialized_start = 1152
|
|
50
|
+
_globals["_EVALREASON_PB"]._serialized_end = 1219
|
|
59
51
|
# @@protoc_insertion_point(module_scope)
|