devcycle-python-server-sdk 2.0.0__py3-none-any.whl → 3.0.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.
Files changed (51) hide show
  1. devcycle_python_sdk/VERSION.txt +1 -1
  2. devcycle_python_sdk/__init__.py +3 -2
  3. devcycle_python_sdk/api/backoff.py +11 -0
  4. devcycle_python_sdk/api/bucketing_client.py +17 -25
  5. devcycle_python_sdk/api/config_client.py +116 -0
  6. devcycle_python_sdk/api/event_client.py +95 -0
  7. devcycle_python_sdk/api/local_bucketing.py +436 -0
  8. devcycle_python_sdk/{dvc_cloud_client.py → cloud_client.py} +69 -41
  9. devcycle_python_sdk/exceptions.py +25 -3
  10. devcycle_python_sdk/local_client.py +220 -0
  11. devcycle_python_sdk/managers/__init__.py +0 -0
  12. devcycle_python_sdk/managers/config_manager.py +95 -0
  13. devcycle_python_sdk/managers/event_queue_manager.py +223 -0
  14. devcycle_python_sdk/models/bucketed_config.py +150 -0
  15. devcycle_python_sdk/models/event.py +111 -8
  16. devcycle_python_sdk/models/feature.py +12 -0
  17. devcycle_python_sdk/models/platform_data.py +33 -0
  18. devcycle_python_sdk/models/user.py +52 -7
  19. devcycle_python_sdk/models/variable.py +17 -5
  20. devcycle_python_sdk/options.py +106 -0
  21. devcycle_python_sdk/protobuf/__init__.py +0 -0
  22. devcycle_python_sdk/protobuf/utils.py +129 -0
  23. devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +46 -0
  24. devcycle_python_sdk/py.typed +0 -0
  25. devcycle_python_sdk/util/version.py +1 -1
  26. {devcycle_python_server_sdk-2.0.0.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/METADATA +3 -2
  27. devcycle_python_server_sdk-3.0.0.dist-info/RECORD +55 -0
  28. example/{example.py → cloud_client_example.py} +24 -20
  29. example/local_bucketing_client_example.py +74 -0
  30. test/api/__init__.py +0 -0
  31. test/{test_bucketing_client.py → api/test_bucketing_client.py} +10 -5
  32. test/api/test_config_client.py +133 -0
  33. test/api/test_event_client.py +97 -0
  34. test/api/test_local_bucketing.py +392 -0
  35. test/fixture/__init__.py +0 -0
  36. test/fixture/data.py +57 -0
  37. test/managers/__init__.py +0 -0
  38. test/managers/test_config_manager.py +132 -0
  39. test/managers/test_event_queue_manager.py +271 -0
  40. test/models/__init__.py +0 -0
  41. test/models/test_bucketed_config.py +26 -0
  42. test/{test_dvc_cloud_client.py → test_cloud_client.py} +11 -17
  43. test/test_local_client.py +397 -0
  44. test/util/__init__.py +0 -0
  45. test/util/test_utils.py +183 -0
  46. test/{test_version.py → util/test_version.py} +2 -2
  47. devcycle_python_sdk/dvc_options.py +0 -18
  48. devcycle_python_server_sdk-2.0.0.dist-info/RECORD +0 -26
  49. {devcycle_python_server_sdk-2.0.0.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/LICENSE +0 -0
  50. {devcycle_python_server_sdk-2.0.0.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/WHEEL +0 -0
  51. {devcycle_python_server_sdk-2.0.0.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,123 @@
1
1
  # ruff: noqa: N815
2
2
  from dataclasses import dataclass, field
3
- from typing import Dict, Optional
4
- from time import time
3
+ from typing import Dict, Optional, Any, List
4
+ from datetime import datetime, timezone
5
+
6
+ from .user import DevCycleUser
7
+
8
+
9
+ class EventType:
10
+ VariableEvaluated = "variableEvaluated"
11
+ AggVariableEvaluated = "aggVariableEvaluated"
12
+ VariableDefaulted = "variableDefaulted"
13
+ AggVariableDefaulted = "aggVariableDefaulted"
14
+ CustomEvent = "customEvent"
5
15
 
6
16
 
7
17
  @dataclass(order=False)
8
- class Event:
18
+ class DevCycleEvent:
9
19
  type: Optional[str] = None
10
20
  target: Optional[str] = None
11
- date: int = field(default_factory=lambda: int(time()))
21
+ date: datetime = field(default_factory=lambda: datetime.utcnow())
12
22
  value: Optional[int] = None
13
- metaData: Dict[str, str] = field(default_factory=dict)
23
+ metaData: Optional[Dict[str, str]] = None
14
24
 
15
- def to_json(self):
16
- return {
25
+ def to_json(self, use_bucketing_api_format: bool = False) -> Dict[str, Any]:
26
+ json_dict = {
17
27
  key: getattr(self, key)
18
28
  for key in self.__dataclass_fields__
19
- if getattr(self, key) is not None
29
+ if getattr(self, key) is not None and key != "date"
20
30
  }
31
+ if self.date:
32
+ if use_bucketing_api_format:
33
+ # convert to timestamp in milliseconds as required by the bucketing API
34
+ json_dict["date"] = int(self.date.timestamp() * 1000)
35
+ else:
36
+ # convert to UTC and format as ISO string
37
+ json_dict["date"] = self.date.astimezone(tz=timezone.utc).isoformat()
38
+ return json_dict
39
+
40
+
41
+ @dataclass(order=False)
42
+ class RequestEvent:
43
+ """
44
+ An event generated by local bucketing event that can be sent to the Events API
45
+ Not interfaced by developers, purely for internal use
46
+ """
47
+
48
+ type: str
49
+ user_id: str
50
+ # will be a timestamp in iso format
51
+ clientDate: str
52
+ # will be a timestamp in iso format
53
+ date: str
54
+ target: Optional[str] = None
55
+ customType: Optional[str] = None
56
+ value: Optional[float] = 0
57
+ featureVars: Dict[str, str] = field(default_factory=dict)
58
+ metaData: Optional[Dict[str, str]] = None
59
+
60
+ def to_json(self):
61
+ json_obj = {}
62
+ for key in self.__dataclass_fields__:
63
+ json_obj[key] = getattr(self, key)
64
+
65
+ return json_obj
66
+
67
+ @classmethod
68
+ def from_json(cls, data: dict) -> "RequestEvent":
69
+ return cls(
70
+ type=data["type"],
71
+ user_id=data["user_id"],
72
+ clientDate=data["clientDate"],
73
+ date=data["date"],
74
+ target=data.get("target"),
75
+ customType=data.get("customType"),
76
+ value=data.get("value", 0),
77
+ featureVars=data.get("featureVars", {}),
78
+ metaData=data.get("metaData"),
79
+ )
80
+
81
+
82
+ @dataclass()
83
+ class UserEventsBatchRecord:
84
+ """
85
+ A collection of events generated by local bucketing library for a single user
86
+ """
87
+
88
+ user: DevCycleUser
89
+ events: List[RequestEvent]
90
+
91
+ def to_json(self) -> Dict[str, Any]:
92
+ return {
93
+ "user": self.user.to_json(),
94
+ "events": [event.to_json() for event in self.events],
95
+ }
96
+
97
+ @classmethod
98
+ def from_json(cls, data: dict) -> "UserEventsBatchRecord":
99
+ return cls(
100
+ user=DevCycleUser.from_json(data["user"]),
101
+ events=[RequestEvent.from_json(element) for element in data["events"]],
102
+ )
103
+
104
+
105
+ @dataclass()
106
+ class FlushPayload:
107
+ """
108
+ A collection of events exported by the local bucketing library that can be sent to the Events API as a batch
109
+ """
110
+
111
+ payloadId: str
112
+ records: List[UserEventsBatchRecord]
113
+ eventCount: int
114
+
115
+ @classmethod
116
+ def from_json(cls, data: dict) -> "FlushPayload":
117
+ return cls(
118
+ payloadId=data["payloadId"],
119
+ eventCount=data["eventCount"],
120
+ records=[
121
+ UserEventsBatchRecord.from_json(element) for element in data["records"]
122
+ ],
123
+ )
@@ -19,3 +19,15 @@ class Feature:
19
19
  for key in self.__dataclass_fields__
20
20
  if getattr(self, key) is not None
21
21
  }
22
+
23
+ @classmethod
24
+ def from_json(cls, data: dict) -> "Feature":
25
+ return cls(
26
+ _id=data["_id"],
27
+ key=data["key"],
28
+ type=data["type"],
29
+ _variation=data["_variation"],
30
+ variationName=data["variationName"],
31
+ variationKey=data["variationKey"],
32
+ evalReason=data.get("evalReason"),
33
+ )
@@ -0,0 +1,33 @@
1
+ # ruff: noqa: N815
2
+ import platform
3
+ import socket
4
+ from dataclasses import dataclass
5
+ from devcycle_python_sdk.util.version import sdk_version
6
+
7
+
8
+ @dataclass(order=False)
9
+ class PlatformData:
10
+ sdkType: str
11
+ sdkVersion: str
12
+ platformVersion: str
13
+ deviceModel: str
14
+ platform: str
15
+ hostname: str
16
+
17
+ def to_json(self):
18
+ return {
19
+ key: getattr(self, key)
20
+ for key in self.__dataclass_fields__
21
+ if getattr(self, key) is not None
22
+ }
23
+
24
+
25
+ def default_platform_data() -> PlatformData:
26
+ return PlatformData(
27
+ sdkType="server",
28
+ sdkVersion=sdk_version(),
29
+ platformVersion=platform.python_version(),
30
+ deviceModel=platform.platform(),
31
+ platform="Python",
32
+ hostname=socket.gethostname(),
33
+ )
@@ -1,11 +1,11 @@
1
1
  # ruff: noqa: N815
2
2
  from dataclasses import dataclass, field
3
- from time import time
3
+ from datetime import datetime, timezone
4
4
  from typing import Dict, Optional
5
5
 
6
6
 
7
7
  @dataclass(order=False)
8
- class User:
8
+ class DevCycleUser:
9
9
  user_id: str
10
10
  email: Optional[str] = None
11
11
  name: Optional[str] = None
@@ -13,10 +13,10 @@ class User:
13
13
  country: Optional[str] = None
14
14
  appVersion: Optional[str] = None
15
15
  appBuild: Optional[str] = None
16
- customData: Dict[str, str] = field(default_factory=dict)
17
- privateCustomData: Dict[str, str] = field(default_factory=dict)
18
- createdDate: int = field(default_factory=lambda: int(time()))
19
- lastSeenDate: Optional[int] = None # TODO: accept datetime
16
+ customData: Optional[Dict[str, str]] = None
17
+ createdDate: datetime = field(default_factory=lambda: datetime.utcnow())
18
+ privateCustomData: Optional[Dict[str, str]] = None
19
+ lastSeenDate: Optional[datetime] = None
20
20
  platform: Optional[str] = None
21
21
  platformVersion: Optional[str] = None
22
22
  deviceModel: Optional[str] = None
@@ -24,8 +24,53 @@ class User:
24
24
  sdkVersion: Optional[str] = None
25
25
 
26
26
  def to_json(self):
27
- return {
27
+ json_dict = {
28
28
  key: getattr(self, key)
29
29
  for key in self.__dataclass_fields__
30
30
  if getattr(self, key) is not None
31
+ and key not in ["createdDate", "lastSeenDate"]
31
32
  }
33
+
34
+ if self.createdDate:
35
+ json_dict["createdDate"] = self.createdDate.astimezone(
36
+ tz=timezone.utc
37
+ ).isoformat()
38
+ if self.lastSeenDate:
39
+ json_dict["lastSeenDate"] = self.lastSeenDate.astimezone(
40
+ tz=timezone.utc
41
+ ).isoformat()
42
+ return json_dict
43
+
44
+ @classmethod
45
+ def from_json(cls, data: dict) -> "DevCycleUser":
46
+ if "createdDate" in data:
47
+ created_date = datetime.fromisoformat(
48
+ data["createdDate"].replace("Z", "+00:00")
49
+ )
50
+ else:
51
+ created_date = datetime.utcnow()
52
+
53
+ last_seen_date = None
54
+ if "lastSeenDate" in data:
55
+ last_seen_date = datetime.fromisoformat(
56
+ data["lastSeenDate"].replace("Z", "+00:00")
57
+ )
58
+
59
+ return cls(
60
+ user_id=data["user_id"],
61
+ email=data.get("email"),
62
+ name=data.get("name"),
63
+ language=data.get("language"),
64
+ country=data.get("country"),
65
+ appVersion=data.get("appVersion"),
66
+ appBuild=data.get("appBuild"),
67
+ customData=data.get("customData"),
68
+ privateCustomData=data.get("privateCustomData"),
69
+ createdDate=created_date,
70
+ lastSeenDate=last_seen_date,
71
+ platform=data.get("platform"),
72
+ platformVersion=data.get("platformVersion"),
73
+ deviceModel=data.get("deviceModel"),
74
+ sdkType=data.get("sdkType"),
75
+ sdkVersion=data.get("sdkVersion"),
76
+ )
@@ -26,12 +26,12 @@ def determine_variable_type(value: Any) -> str:
26
26
  @dataclass(order=False)
27
27
  class Variable:
28
28
  _id: Optional[str]
29
- key: Optional[str]
30
- type: Optional[str]
31
- value: Optional[Any] = None
29
+ key: str
30
+ type: str
31
+ value: Any = None
32
32
  isDefaulted: Optional[bool] = False
33
- defaultValue: Optional[Any] = None
34
- # evalReason: Optional[str] = None
33
+ defaultValue: Any = None
34
+ evalReason: Optional[str] = None
35
35
 
36
36
  def to_json(self):
37
37
  return {
@@ -40,6 +40,18 @@ class Variable:
40
40
  if getattr(self, key) is not None
41
41
  }
42
42
 
43
+ @classmethod
44
+ def from_json(cls, data: dict) -> "Variable":
45
+ return cls(
46
+ _id=data["_id"],
47
+ key=data["key"],
48
+ type=data["type"],
49
+ value=data["value"],
50
+ isDefaulted=data.get("isDefaulted", None),
51
+ defaultValue=data.get("defaultValue"),
52
+ evalReason=data.get("evalReason"),
53
+ )
54
+
43
55
  @staticmethod
44
56
  def create_default_variable(key: str, default_value: Any) -> "Variable":
45
57
  var_type = determine_variable_type(default_value)
@@ -0,0 +1,106 @@
1
+ import logging
2
+ from typing import Callable, Optional, Dict, Any
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+
7
+ class DevCycleCloudOptions:
8
+ """
9
+ Options for configuring the DevCycle Cloud SDK.
10
+ """
11
+
12
+ def __init__(
13
+ self,
14
+ enable_edge_db: bool = False,
15
+ bucketing_api_uri: str = "https://bucketing-api.devcycle.com/",
16
+ request_timeout: int = 5, # seconds
17
+ request_retries: int = 5,
18
+ retry_delay: int = 200, # milliseconds
19
+ ):
20
+ self.enable_edge_db = enable_edge_db
21
+ self.bucketing_api_uri = bucketing_api_uri
22
+ self.request_timeout = request_timeout
23
+ self.request_retries = request_retries
24
+ self.retry_delay = retry_delay
25
+
26
+
27
+ class DevCycleLocalOptions:
28
+ """
29
+ Options for configuring the DevCycle Local Bucketing SDK.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ config_cdn_uri: str = "https://config-cdn.devcycle.com/",
35
+ config_request_timeout_ms: int = 5000,
36
+ config_polling_interval_ms: int = 1000,
37
+ config_retry_delay_ms: int = 200, # milliseconds
38
+ on_client_initialized: Optional[Callable] = None,
39
+ events_api_uri: str = "https://events.devcycle.com/",
40
+ max_event_queue_size: int = 2000,
41
+ event_flush_interval_ms: int = 10000,
42
+ flush_event_queue_size: int = 1000,
43
+ event_request_chunk_size: int = 100,
44
+ event_request_timeout_ms: int = 10000,
45
+ event_retry_delay_ms: int = 200, # milliseconds
46
+ disable_automatic_event_logging: bool = False,
47
+ disable_custom_event_logging: bool = False,
48
+ ):
49
+ self.events_api_uri = events_api_uri
50
+ self.config_cdn_uri = config_cdn_uri
51
+ self.config_request_timeout_ms = config_request_timeout_ms
52
+ self.config_polling_interval_ms = config_polling_interval_ms
53
+ self.max_event_queue_size = max_event_queue_size
54
+ self.event_flush_interval_ms = event_flush_interval_ms
55
+ self.flush_event_queue_size = flush_event_queue_size
56
+ self.event_request_chunk_size = event_request_chunk_size
57
+ self.disable_automatic_event_logging = disable_automatic_event_logging
58
+ self.disable_custom_event_logging = disable_custom_event_logging
59
+ self.config_retry_delay_ms = config_retry_delay_ms
60
+ self.on_client_initialized = on_client_initialized
61
+ self.event_request_timeout_ms = event_request_timeout_ms
62
+ self.event_retry_delay_ms = event_retry_delay_ms
63
+
64
+ if self.flush_event_queue_size >= self.max_event_queue_size:
65
+ logger.warning(
66
+ f"flush_event_queue_size: {self.flush_event_queue_size} must be smaller than max_event_queue_size: {self.max_event_queue_size}"
67
+ )
68
+ self.flush_event_queue_size = self.max_event_queue_size - 1
69
+
70
+ if self.event_request_chunk_size > self.flush_event_queue_size:
71
+ logger.warning(
72
+ f"event_request_chunk_size: {self.event_request_chunk_size} must be smaller than flush_event_queue_size: {self.flush_event_queue_size}"
73
+ )
74
+ self.event_request_chunk_size = 100
75
+
76
+ if self.event_request_chunk_size > self.max_event_queue_size:
77
+ logger.warning(
78
+ f"event_request_chunk_size: {self.event_request_chunk_size} must be smaller than max_event_queue_size: { self.max_event_queue_size}"
79
+ )
80
+ self.event_request_chunk_size = 100
81
+
82
+ if self.flush_event_queue_size > 20000:
83
+ logger.warning(
84
+ f"flush_event_queue_size: {self.flush_event_queue_size} must be smaller than 20,000"
85
+ )
86
+ self.flush_event_queue_size = 20000
87
+
88
+ if self.max_event_queue_size > 20000:
89
+ logger.warning(
90
+ f"max_event_queue_size: {self.max_event_queue_size} must be smaller than 20,000"
91
+ )
92
+ self.max_event_queue_size = 20000
93
+
94
+ def event_queue_options(self) -> Dict[str, Any]:
95
+ """
96
+ Returns a read-only view of the options that are relevant to the event subsystem
97
+ """
98
+ return {
99
+ "flushEventsMS": self.event_flush_interval_ms,
100
+ "disableAutomaticEventLogging": self.disable_automatic_event_logging,
101
+ "disableCustomEventLogging": self.disable_custom_event_logging,
102
+ "maxEventsPerFlush": self.max_event_queue_size,
103
+ "minEventsPerFlush": self.flush_event_queue_size,
104
+ "eventRequestChunkSize": self.event_request_chunk_size,
105
+ "eventsAPIBasePath": self.events_api_uri,
106
+ }
File without changes
@@ -0,0 +1,129 @@
1
+ import json
2
+ import logging
3
+ import math
4
+
5
+ from typing import Any, Optional
6
+
7
+ from devcycle_python_sdk.models.variable import TypeEnum, Variable
8
+ from devcycle_python_sdk.models.user import DevCycleUser
9
+
10
+ import devcycle_python_sdk.protobuf.variableForUserParams_pb2 as pb2
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def create_nullable_double(val: Optional[float]) -> pb2.NullableDouble: # type: ignore
16
+ if val and not math.isnan(val):
17
+ return pb2.NullableDouble(value=val, isNull=False) # type: ignore
18
+ else:
19
+ return pb2.NullableDouble(isNull=True) # type: ignore
20
+
21
+
22
+ def create_nullable_string(val: Optional[str]) -> pb2.NullableString: # type: ignore
23
+ if val is None:
24
+ return pb2.NullableString(isNull=True) # type: ignore
25
+ else:
26
+ return pb2.NullableString(value=val, isNull=False) # type: ignore
27
+
28
+
29
+ def create_nullable_custom_data(val: Optional[dict]) -> pb2.NullableCustomData: # type: ignore
30
+ if val:
31
+ values = dict()
32
+ for key, value in val.items():
33
+ if value is None:
34
+ values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Null) # type: ignore
35
+ elif isinstance(value, bool):
36
+ values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Bool, boolValue=value) # type: ignore
37
+ elif isinstance(value, str):
38
+ values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Str, stringValue=value) # type: ignore
39
+ elif isinstance(value, (int, float)):
40
+ values[key] = pb2.CustomDataValue(type=pb2.CustomDataType.Num, doubleValue=value) # type: ignore
41
+ else:
42
+ logger.warning(
43
+ f"Custom Data contains data type that can't be written, will be ignored. Key: {key}, Type: {str(type(value))}"
44
+ )
45
+
46
+ return pb2.NullableCustomData(value=values, isNull=False) # type: ignore
47
+ else:
48
+ return pb2.NullableCustomData(isNull=True) # type: ignore
49
+
50
+
51
+ def convert_type_enum_to_variable_type(var_type: str) -> pb2.VariableType_PB:
52
+ if var_type == TypeEnum.BOOLEAN:
53
+ return pb2.VariableType_PB.Boolean
54
+ elif var_type == TypeEnum.STRING:
55
+ return pb2.VariableType_PB.String
56
+ elif var_type == TypeEnum.NUMBER:
57
+ return pb2.VariableType_PB.Number
58
+ elif var_type == TypeEnum.JSON:
59
+ return pb2.VariableType_PB.JSON
60
+ else:
61
+ raise ValueError("Unknown type: " + str(var_type))
62
+
63
+
64
+ def create_dvcuser_pb(user: DevCycleUser) -> pb2.DVCUser_PB: # type: ignore
65
+ app_build = float("nan")
66
+ if user.appBuild:
67
+ try:
68
+ app_build = float(user.appBuild)
69
+ except ValueError:
70
+ pass
71
+
72
+ return pb2.DVCUser_PB( # type: ignore
73
+ user_id=user.user_id,
74
+ email=create_nullable_string(user.email),
75
+ name=create_nullable_string(user.name),
76
+ language=create_nullable_string(user.language),
77
+ country=create_nullable_string(user.country),
78
+ appVersion=create_nullable_string(user.appVersion),
79
+ appBuild=create_nullable_double(app_build),
80
+ customData=create_nullable_custom_data(user.customData),
81
+ privateCustomData=create_nullable_custom_data(user.privateCustomData),
82
+ )
83
+
84
+
85
+ def create_variable(sdk_variable: pb2.SDKVariable_PB, default_value: Any) -> Variable: # type: ignore
86
+ if sdk_variable.type == pb2.VariableType_PB.Boolean: # type: ignore
87
+ return Variable(
88
+ _id=None,
89
+ value=sdk_variable.boolValue,
90
+ key=sdk_variable.key,
91
+ type=TypeEnum.BOOLEAN,
92
+ isDefaulted=False,
93
+ defaultValue=default_value,
94
+ )
95
+
96
+ elif sdk_variable.type == pb2.VariableType_PB.String: # type: ignore
97
+ return Variable(
98
+ _id=None,
99
+ value=sdk_variable.stringValue,
100
+ key=sdk_variable.key,
101
+ type=TypeEnum.STRING,
102
+ isDefaulted=False,
103
+ defaultValue=default_value,
104
+ )
105
+
106
+ elif sdk_variable.type == pb2.VariableType_PB.Number: # type: ignore
107
+ return Variable(
108
+ _id=None,
109
+ value=sdk_variable.doubleValue,
110
+ key=sdk_variable.key,
111
+ type=TypeEnum.NUMBER,
112
+ isDefaulted=False,
113
+ defaultValue=default_value,
114
+ )
115
+
116
+ elif sdk_variable.type == pb2.VariableType_PB.JSON: # type: ignore
117
+ json_data = json.loads(sdk_variable.stringValue)
118
+
119
+ return Variable(
120
+ _id=None,
121
+ value=json_data,
122
+ key=sdk_variable.key,
123
+ type=TypeEnum.JSON,
124
+ isDefaulted=False,
125
+ defaultValue=default_value,
126
+ )
127
+
128
+ else:
129
+ raise ValueError("Unknown type: " + sdk_variable.type)
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: variableForUserParams.proto
4
+ """Generated protocol buffer code."""
5
+ from google.protobuf.internal import builder as _builder
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ # @@protoc_insertion_point(imports)
10
+
11
+ _sym_db = _symbol_database.Default()
12
+
13
+
14
+
15
+
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:
21
+
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
46
+ # @@protoc_insertion_point(module_scope)
File without changes
@@ -5,5 +5,5 @@ def sdk_version() -> str:
5
5
  """
6
6
  Returns the current version of this SDK as a semantic version string
7
7
  """
8
- with open(os.path.dirname(__file__) + '/../VERSION.txt', 'r') as version_file:
8
+ with open(os.path.dirname(__file__) + "/../VERSION.txt", "r") as version_file:
9
9
  return version_file.read().strip()
@@ -1,15 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devcycle-python-server-sdk
3
- Version: 2.0.0
3
+ Version: 3.0.0
4
4
  Summary: DevCycle Python SDK
5
5
  Home-page: https://github.com/devcycleHQ/python-server-sdk
6
6
  Author-email:
7
7
  Keywords: DevCycle
8
8
  License-File: LICENSE
9
- Requires-Dist: python-dateutil (>=2.5.3)
10
9
  Requires-Dist: setuptools (>=21.0.0)
11
10
  Requires-Dist: urllib3 (>=1.15.1)
12
11
  Requires-Dist: requests (~=2.31.0)
12
+ Requires-Dist: wasmtime (==9.0.0)
13
+ Requires-Dist: protobuf (>=4.23.3)
13
14
 
14
15
  The DevCycle Python SDK used for feature management.
15
16