Skip to content

Commit 519c414

Browse files
authored
feat: add config metadata to eval hooks for local bucketing (#98)
* feat: add config metadata to eval hooks for local bucketing * chore: linter fixes
1 parent 4486168 commit 519c414

17 files changed

+244
-62
lines changed

devcycle_python_sdk/api/local_bucketing.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from devcycle_python_sdk.models.user import DevCycleUser
2929
from devcycle_python_sdk.models.variable import Variable, determine_variable_type
3030
from devcycle_python_sdk.models.event import FlushPayload
31+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
3132

3233
logger = logging.getLogger(__name__)
3334

@@ -140,6 +141,7 @@ def __console_log_func(message_ptr) -> None:
140141
"generateBucketedConfigForUserUTF8"
141142
)
142143
self.VariableForUserProtobuf = self._get_export("variableForUser_PB")
144+
self.getConfigMetadata = self._get_export("getConfigMetadata")
143145

144146
# Extract variable type enum values from WASM
145147
self.variable_type_map = {
@@ -357,6 +359,14 @@ def store_config(self, config_json: str) -> None:
357359
config_addr = self._new_assembly_script_byte_array(data)
358360
self.setConfigDataUTF8(self.wasm_store, self.sdk_key_addr, config_addr)
359361

362+
def get_config_metadata(self) -> Optional[ConfigMetadata]:
363+
with self.wasm_lock:
364+
config_addr = self.getConfigMetadata(self.wasm_store, self.sdk_key_addr)
365+
config_bytes = self._read_assembly_script_string(config_addr)
366+
config_data = json.loads(config_bytes.encode("utf-8"))
367+
368+
return ConfigMetadata.from_json(config_data)
369+
360370
def set_platform_data(self, platform_json: str) -> None:
361371
with self.wasm_lock:
362372
data = platform_json.encode("utf-8")
1.47 KB
Binary file not shown.

devcycle_python_sdk/local_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ def variable(self, user: DevCycleUser, key: str, default_value: Any) -> Variable
150150
key, default_value, DefaultReasonDetails.MISSING_CONFIG
151151
)
152152

153-
context = HookContext(key, user, default_value)
153+
config_metadata = self.local_bucketing.get_config_metadata()
154+
155+
context = HookContext(key, user, default_value, config_metadata)
154156
variable = Variable.create_default_variable(
155157
key=key, default_value=default_value
156158
)

devcycle_python_sdk/managers/config_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from wsgiref.handlers import format_date_time
1717
from devcycle_python_sdk.options import DevCycleLocalOptions
1818
from devcycle_python_sdk.managers.sse_manager import SSEManager
19+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
1920

2021
logger = logging.getLogger(__name__)
2122

@@ -108,6 +109,9 @@ def _get_config(self, last_modified: Optional[float] = None):
108109
)
109110
self._polling_enabled = False
110111

112+
def get_config_metadata(self) -> Optional[ConfigMetadata]:
113+
return self._local_bucketing.get_config_metadata()
114+
111115
def run(self):
112116
while self._polling_enabled:
113117
try:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
import json
5+
6+
7+
class ConfigMetadata:
8+
def __init__(
9+
self,
10+
project: ProjectMetadata,
11+
environment: EnvironmentMetadata,
12+
):
13+
self.project = project
14+
self.environment = environment
15+
16+
def to_json(self) -> str:
17+
return json.dumps(self, default=lambda o: o.__dict__)
18+
19+
@staticmethod
20+
def from_json(json_obj: Optional[Dict[str, Any]]) -> Optional["ConfigMetadata"]:
21+
if json_obj is None:
22+
return None
23+
project = ProjectMetadata.from_json(json_obj.get("project"))
24+
environment = EnvironmentMetadata.from_json(json_obj.get("environment"))
25+
26+
if project is None or environment is None:
27+
return None
28+
29+
return ConfigMetadata(
30+
project=project,
31+
environment=environment,
32+
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Dict, Any, Optional
2+
3+
4+
class EnvironmentMetadata:
5+
def __init__(
6+
self,
7+
id: str,
8+
key: str,
9+
):
10+
self.id = id
11+
self.key = key
12+
13+
@staticmethod
14+
def from_json(
15+
json_obj: Optional[Dict[str, Any]],
16+
) -> Optional["EnvironmentMetadata"]:
17+
if json_obj is None:
18+
return None
19+
return EnvironmentMetadata(
20+
id=json_obj["id"],
21+
key=json_obj["key"],
22+
)
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
from typing import Any
1+
from typing import Any, Optional
22

33
from devcycle_python_sdk.models.user import DevCycleUser
4+
from devcycle_python_sdk.models.config_metadata import ConfigMetadata
45

56

67
class HookContext:
7-
def __init__(self, key: str, user: DevCycleUser, default_value: Any):
8+
def __init__(
9+
self,
10+
key: str,
11+
user: DevCycleUser,
12+
default_value: Any,
13+
config_metadata: Optional[ConfigMetadata] = None,
14+
):
815
self.key = key
916
self.default_value = default_value
1017
self.user = user
18+
self.config_metadata = config_metadata
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Dict, Any, Optional
2+
3+
4+
class ProjectMetadata:
5+
def __init__(
6+
self,
7+
id: str,
8+
key: str,
9+
):
10+
self.id = id
11+
self.key = key
12+
13+
@staticmethod
14+
def from_json(json_obj: Optional[Dict[str, Any]]) -> Optional["ProjectMetadata"]:
15+
if json_obj is None:
16+
return None
17+
return ProjectMetadata(
18+
id=json_obj["id"],
19+
key=json_obj["key"],
20+
)

devcycle_python_sdk/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def __init__(
9090

9191
if self.event_request_chunk_size > self.max_event_queue_size:
9292
logger.warning(
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}"
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}"
9494
)
9595
self.event_request_chunk_size = 100
9696

devcycle_python_sdk/protobuf/utils.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ def create_nullable_custom_data(val: Optional[dict]) -> pb2.NullableCustomData:
3434
if value is None:
3535
values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Null) # type: ignore
3636
elif isinstance(value, bool):
37-
values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Bool, boolValue=value) # type: ignore
37+
values[key] = pb2.CustomDataValue(
38+
type=pb2.CustomDataType.Bool, boolValue=value
39+
) # type: ignore
3840
elif isinstance(value, str):
39-
values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Str, stringValue=value) # type: ignore
41+
values[key] = pb2.CustomDataValue(
42+
type=pb2.CustomDataType.Str, stringValue=value
43+
) # type: ignore
4044
elif isinstance(value, (int, float)):
41-
values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Num, doubleValue=value) # type: ignore
45+
values[key] = pb2.CustomDataValue(
46+
type=pb2.CustomDataType.Num, doubleValue=value
47+
) # type: ignore
4248
else:
4349
logger.warning(
4450
f"Custom Data contains data type that can't be written, will be ignored. Key: {key}, Type: {str(type(value))}"

0 commit comments

Comments
 (0)