devcycle-python-server-sdk 3.7.0__py3-none-any.whl → 3.8.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.7.0
1
+ 3.8.0
@@ -1,16 +1,21 @@
1
+ import json
2
+ import logging
1
3
  import threading
2
4
  import time
3
- import logging
4
- import json
5
+ from datetime import datetime
5
6
  from typing import Optional
6
7
 
7
- from devcycle_python_sdk.options import DevCycleLocalOptions
8
- from devcycle_python_sdk.api.local_bucketing import LocalBucketing
8
+ import ld_eventsource.actions
9
+
9
10
  from devcycle_python_sdk.api.config_client import ConfigAPIClient
11
+ from devcycle_python_sdk.api.local_bucketing import LocalBucketing
10
12
  from devcycle_python_sdk.exceptions import (
11
13
  CloudClientUnauthorizedError,
12
14
  CloudClientError,
13
15
  )
16
+ from wsgiref.handlers import format_date_time
17
+ from devcycle_python_sdk.options import DevCycleLocalOptions
18
+ from devcycle_python_sdk.managers.sse_manager import SSEManager
14
19
 
15
20
  logger = logging.getLogger(__name__)
16
21
 
@@ -27,7 +32,9 @@ class EnvironmentConfigManager(threading.Thread):
27
32
  self._sdk_key = sdk_key
28
33
  self._options = options
29
34
  self._local_bucketing = local_bucketing
30
-
35
+ self._sse_manager: Optional[SSEManager] = None
36
+ self._sse_polling_interval = 1000 * 60 * 15 * 60
37
+ self._sse_connected = False
31
38
  self._config: Optional[dict] = None
32
39
  self._config_etag: Optional[str] = None
33
40
  self._config_lastmodified: Optional[str] = None
@@ -41,10 +48,15 @@ class EnvironmentConfigManager(threading.Thread):
41
48
  def is_initialized(self) -> bool:
42
49
  return self._config is not None
43
50
 
44
- def _get_config(self):
51
+ def _get_config(self, last_modified: Optional[float] = None):
45
52
  try:
53
+ lm_header = self._config_lastmodified
54
+ if last_modified is not None:
55
+ lm_timestamp = datetime.fromtimestamp(last_modified)
56
+ lm_header = format_date_time(time.mktime(lm_timestamp.timetuple()))
57
+
46
58
  new_config, new_etag, new_lastmodified = self._config_api_client.get_config(
47
- config_etag=self._config_etag, last_modified=self._config_lastmodified
59
+ config_etag=self._config_etag, last_modified=lm_header
48
60
  )
49
61
 
50
62
  # Abort early if the last modified is before the sent one.
@@ -68,6 +80,14 @@ class EnvironmentConfigManager(threading.Thread):
68
80
 
69
81
  json_config = json.dumps(self._config)
70
82
  self._local_bucketing.store_config(json_config)
83
+ if not self._options.disable_realtime_updates:
84
+ if self._sse_manager is None:
85
+ self._sse_manager = SSEManager(
86
+ self.sse_state,
87
+ self.sse_error,
88
+ self.sse_message,
89
+ )
90
+ self._sse_manager.update(self._config)
71
91
 
72
92
  if (
73
93
  trigger_on_client_initialized
@@ -98,7 +118,32 @@ class EnvironmentConfigManager(threading.Thread):
98
118
  logger.warning(
99
119
  f"DevCycle: Error polling for config changes: {str(e)}"
100
120
  )
101
- time.sleep(self._options.config_polling_interval_ms / 1000.0)
121
+ if self._sse_connected:
122
+ time.sleep(self._sse_polling_interval / 1000.0)
123
+ else:
124
+ time.sleep(self._options.config_polling_interval_ms / 1000.0)
125
+
126
+ def sse_message(self, message: ld_eventsource.actions.Event):
127
+ if self._sse_connected is False:
128
+ self._sse_connected = True
129
+ logger.info("DevCycle: Connected to SSE stream")
130
+ logger.info(f"DevCycle: Received message: {message.data}")
131
+ sse_message = json.loads(message.data)
132
+ dvc_data = json.loads(sse_message.get("data"))
133
+ if (
134
+ dvc_data.get("type") == "refetchConfig"
135
+ or dvc_data.get("type") == ""
136
+ or dvc_data.get("type") is None
137
+ ):
138
+ logger.info("DevCycle: Received refetchConfig message - updating config")
139
+ self._get_config(dvc_data["lastModified"])
140
+
141
+ def sse_error(self, error: ld_eventsource.actions.Fault):
142
+ logger.warning(f"DevCycle: Received SSE error: {error}")
143
+
144
+ def sse_state(self, state: ld_eventsource.actions.Start):
145
+ self._sse_connected = True
146
+ logger.info("DevCycle: Connected to SSE stream")
102
147
 
103
148
  def close(self):
104
149
  self._polling_enabled = False
@@ -0,0 +1,63 @@
1
+ import threading
2
+
3
+ import ld_eventsource
4
+ import ld_eventsource.actions
5
+ import ld_eventsource.config
6
+ from typing import Callable
7
+
8
+
9
+ class SSEManager:
10
+ def __init__(
11
+ self,
12
+ handlestate: Callable[[ld_eventsource.actions.Start], None],
13
+ handleerror: Callable[[ld_eventsource.actions.Fault], None],
14
+ handlemessage: Callable[[ld_eventsource.actions.Event], None],
15
+ ):
16
+ self.client: ld_eventsource.SSEClient = None
17
+ self.url = ""
18
+ self.handlestate = handlestate
19
+ self.handleerror = handleerror
20
+ self.handlemessage = handlemessage
21
+
22
+ self.read_thread = threading.Thread(
23
+ target=self.read_events,
24
+ args=(self.handlestate, self.handleerror, self.handlemessage),
25
+ )
26
+
27
+ def read_events(
28
+ self,
29
+ handlestate: Callable[[ld_eventsource.actions.Start], None],
30
+ handleerror: Callable[[ld_eventsource.actions.Fault], None],
31
+ handlemessage: Callable[[ld_eventsource.actions.Event], None],
32
+ ):
33
+ self.client.start()
34
+ for event in self.client.all:
35
+ if isinstance(event, ld_eventsource.actions.Start):
36
+ handlestate(event)
37
+ elif isinstance(event, ld_eventsource.actions.Fault):
38
+ handleerror(event)
39
+ elif isinstance(event, ld_eventsource.actions.Event):
40
+ handlemessage(event)
41
+
42
+ def update(self, config: dict):
43
+ if self.use_new_config(config["sse"]):
44
+ self.url = config["sse"]["hostname"] + config["sse"]["path"]
45
+ if self.client is not None:
46
+ self.client.close()
47
+ if self.read_thread.is_alive():
48
+ self.read_thread.join()
49
+ self.client = ld_eventsource.SSEClient(
50
+ connect=ld_eventsource.config.ConnectStrategy.http(self.url),
51
+ error_strategy=ld_eventsource.config.ErrorStrategy.CONTINUE,
52
+ )
53
+ self.read_thread = threading.Thread(
54
+ target=self.read_events,
55
+ args=(self.handlestate, self.handleerror, self.handlemessage),
56
+ )
57
+ self.read_thread.start()
58
+
59
+ def use_new_config(self, config: dict) -> bool:
60
+ new_url = config["hostname"] + config["path"]
61
+ if self.url == "" or self.url is None and new_url != "":
62
+ return True
63
+ return self.url != new_url
@@ -125,7 +125,7 @@ class DevCycleUser:
125
125
  if key == "user_id":
126
126
  continue
127
127
 
128
- if value:
128
+ if value is not None:
129
129
  if key == "email" and isinstance(value, str):
130
130
  user.email = value
131
131
  elif key == "name" and isinstance(value, str):
@@ -45,6 +45,8 @@ class DevCycleLocalOptions:
45
45
  event_retry_delay_ms: int = 200, # milliseconds
46
46
  disable_automatic_event_logging: bool = False,
47
47
  disable_custom_event_logging: bool = False,
48
+ enable_beta_realtime_updates: bool = False,
49
+ disable_realtime_updates: bool = False,
48
50
  ):
49
51
  self.events_api_uri = events_api_uri
50
52
  self.config_cdn_uri = config_cdn_uri
@@ -60,6 +62,12 @@ class DevCycleLocalOptions:
60
62
  self.on_client_initialized = on_client_initialized
61
63
  self.event_request_timeout_ms = event_request_timeout_ms
62
64
  self.event_retry_delay_ms = event_retry_delay_ms
65
+ self.disable_realtime_updates = disable_realtime_updates
66
+
67
+ if enable_beta_realtime_updates:
68
+ logger.warning(
69
+ "DevCycle: `enable_beta_realtime_updates` is deprecated and will be removed in a future release.",
70
+ )
63
71
 
64
72
  if self.flush_event_queue_size >= self.max_event_queue_size:
65
73
  logger.warning(
@@ -0,0 +1,120 @@
1
+ from google.protobuf.internal import containers as _containers
2
+ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
3
+ from google.protobuf import descriptor as _descriptor
4
+ from google.protobuf import message as _message
5
+ from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
6
+
7
+ Bool: CustomDataType
8
+ Boolean: VariableType_PB
9
+ DESCRIPTOR: _descriptor.FileDescriptor
10
+ JSON: VariableType_PB
11
+ Null: CustomDataType
12
+ Num: CustomDataType
13
+ Number: VariableType_PB
14
+ Str: CustomDataType
15
+ String: VariableType_PB
16
+
17
+ class CustomDataValue(_message.Message):
18
+ __slots__ = ["boolValue", "doubleValue", "stringValue", "type"]
19
+ BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
20
+ DOUBLEVALUE_FIELD_NUMBER: _ClassVar[int]
21
+ STRINGVALUE_FIELD_NUMBER: _ClassVar[int]
22
+ TYPE_FIELD_NUMBER: _ClassVar[int]
23
+ boolValue: bool
24
+ doubleValue: float
25
+ stringValue: str
26
+ type: CustomDataType
27
+ def __init__(self, type: _Optional[_Union[CustomDataType, str]] = ..., boolValue: bool = ..., doubleValue: _Optional[float] = ..., stringValue: _Optional[str] = ...) -> None: ...
28
+
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
+ class NullableCustomData(_message.Message):
54
+ __slots__ = ["isNull", "value"]
55
+ class ValueEntry(_message.Message):
56
+ __slots__ = ["key", "value"]
57
+ KEY_FIELD_NUMBER: _ClassVar[int]
58
+ VALUE_FIELD_NUMBER: _ClassVar[int]
59
+ key: str
60
+ value: CustomDataValue
61
+ def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[CustomDataValue, _Mapping]] = ...) -> None: ...
62
+ ISNULL_FIELD_NUMBER: _ClassVar[int]
63
+ VALUE_FIELD_NUMBER: _ClassVar[int]
64
+ isNull: bool
65
+ value: _containers.MessageMap[str, CustomDataValue]
66
+ def __init__(self, value: _Optional[_Mapping[str, CustomDataValue]] = ..., isNull: bool = ...) -> None: ...
67
+
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: ...
75
+
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: ...
83
+
84
+ class SDKVariable_PB(_message.Message):
85
+ __slots__ = ["_id", "boolValue", "doubleValue", "evalReason", "key", "stringValue", "type"]
86
+ BOOLVALUE_FIELD_NUMBER: _ClassVar[int]
87
+ DOUBLEVALUE_FIELD_NUMBER: _ClassVar[int]
88
+ EVALREASON_FIELD_NUMBER: _ClassVar[int]
89
+ KEY_FIELD_NUMBER: _ClassVar[int]
90
+ STRINGVALUE_FIELD_NUMBER: _ClassVar[int]
91
+ TYPE_FIELD_NUMBER: _ClassVar[int]
92
+ _ID_FIELD_NUMBER: _ClassVar[int]
93
+ _id: str
94
+ boolValue: bool
95
+ doubleValue: float
96
+ evalReason: NullableString
97
+ key: str
98
+ 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__ = []
118
+
119
+ class CustomDataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
120
+ __slots__ = []
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.2
2
+ Name: devcycle-python-server-sdk
3
+ Version: 3.8.0
4
+ Summary: DevCycle Python SDK
5
+ Home-page: https://github.com/devcycleHQ/python-server-sdk
6
+ Author-email:
7
+ Keywords: DevCycle
8
+ License-File: LICENSE
9
+ Requires-Dist: setuptools>=21.0.0
10
+ Requires-Dist: urllib3>=1.15.1
11
+ Requires-Dist: requests~=2.31
12
+ Requires-Dist: wasmtime==23.0.0
13
+ Requires-Dist: protobuf>=4.23.3
14
+ Requires-Dist: openfeature-sdk>=0.7.0
15
+ Requires-Dist: launchdarkly-eventsource>=1.2.0
16
+ Requires-Dist: responses~=0.23.1
17
+ Requires-Dist: dataclasses~=0.6
18
+ Dynamic: description
19
+ Dynamic: home-page
20
+ Dynamic: keywords
21
+ Dynamic: requires-dist
22
+ Dynamic: summary
23
+
24
+ The DevCycle Python SDK used for feature management.
25
+
@@ -1,11 +1,11 @@
1
- devcycle_python_sdk/VERSION.txt,sha256=hHRrIKOnL2_4WmKXBuUFB8G9IAkHRWt23YCKKlyg77o,6
1
+ devcycle_python_sdk/VERSION.txt,sha256=iCBq_S2bVspxRmyF0e0UIbHbDnj6AAcml5GPuKmsVO8,6
2
2
  devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
3
3
  devcycle_python_sdk/bucketing-lib.release.wasm,sha256=AJyYTuQs-PRjAiH9yzSgn-iESa9W7Vu0AQHLJHC5QS0,214842
4
4
  devcycle_python_sdk/cloud_client.py,sha256=UMfXvXwO4v3pZW9ZA52ivxPH1Lp4Goy5gpV_s0qCpxQ,7352
5
5
  devcycle_python_sdk/devcycle_client.py,sha256=W5MeOslkXjTqZtoSy_Bbbug8gVoicjxshpKjDteEr6E,917
6
6
  devcycle_python_sdk/exceptions.py,sha256=Et7fS8pEFbHkFY86T2pJoEUstwjh4_rzhJxVH8fzAns,1053
7
7
  devcycle_python_sdk/local_client.py,sha256=WeXt-gWhFsaPjAp2UEmygQhzx_ygx2Jaq0z0yRHaah0,9448
8
- devcycle_python_sdk/options.py,sha256=Ejr6HbdyqzZajFVkEqWLk5XLfdtqCIkvsyWaRt_lgwU,4575
8
+ devcycle_python_sdk/options.py,sha256=WOpeI9U_9Gsf3OFWvoRIBRgJVZEqFxuEYTII71r5L24,4939
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
@@ -14,21 +14,23 @@ devcycle_python_sdk/api/config_client.py,sha256=73aQAq130rTHpQrVDX6n_DwFKz0WuKxR
14
14
  devcycle_python_sdk/api/event_client.py,sha256=WxkuV6geEC9MgG4FN36HeEQQa22ilq2mkOVMZkpiEk4,3356
15
15
  devcycle_python_sdk/api/local_bucketing.py,sha256=_MAMCHviIS1n4GiU-CnnLBeEVX8BUFRqwNbA1IQi84U,16470
16
16
  devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- devcycle_python_sdk/managers/config_manager.py,sha256=YfgGKW4mtMhqdwWZiZ_Y67AYLC9VTpTjYhpDgfGFA4g,3599
17
+ devcycle_python_sdk/managers/config_manager.py,sha256=64ds-8Q9KRKy3lpAKXygPG9tA8ea5wGsFaL6mQ2gsSY,5627
18
18
  devcycle_python_sdk/managers/event_queue_manager.py,sha256=uPy1vO6OAisZjUNAhWELE_g17alTiOcPv42z-WwkGck,8110
19
+ devcycle_python_sdk/managers/sse_manager.py,sha256=aJKHPmvv8SR4DU9IOi-KrUSCxQoHdMwoemb2lSVfmVQ,2369
19
20
  devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
21
  devcycle_python_sdk/models/bucketed_config.py,sha256=NsdT-6RbEuDy5QuK5qlr6UqNKQQnwjoXVYN-uQp0P-E,3999
21
22
  devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
22
23
  devcycle_python_sdk/models/event.py,sha256=-6bhKp72mAgaoSxvhS__G4vvfQ9Psj83fc0Igelzw3o,3713
23
24
  devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
24
25
  devcycle_python_sdk/models/platform_data.py,sha256=kIFevH5trsxI32qp7BW6glUCpipgpaVBKGqJWzQd5Xo,783
25
- devcycle_python_sdk/models/user.py,sha256=57d_n8Yy8g-zsGzVF0hmSf4McPtFPJJ0LLY_ql75FWk,6266
26
+ devcycle_python_sdk/models/user.py,sha256=P2yRqorHosN6ho6lB8eTtz2zY0r4NnU3t8NuLU4nllw,6278
26
27
  devcycle_python_sdk/models/variable.py,sha256=qp00SyCNyfYiMlTwnDSDJ4ZetRF35JHTbYZQOBxFIqY,1711
27
28
  devcycle_python_sdk/open_feature_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
29
  devcycle_python_sdk/open_feature_provider/provider.py,sha256=VjUMhCYiMLixXUSSIjQ7kVCQrls1QD_Nltooe5EjGks,4539
29
30
  devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
31
  devcycle_python_sdk/protobuf/utils.py,sha256=zi-kVx6yvgS2c04cUobLd2IDc_ZqKkWduay9YcpjoGc,4684
31
32
  devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=Zf4-8n67UnulC0Sz90aqLYsXCFEgywOqfiSHuXZomLk,4244
33
+ devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=gjTD1KkBe5NaXixSq78eNTNbjdDjBQ0h5L4dUKKgDWQ,5659
32
34
  devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
33
35
  devcycle_python_sdk/util/strings.py,sha256=gH0Xm3gIXNqn6hczKPk10Jn5_9knuD7OB5MrB7bqztw,252
34
36
  devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
@@ -38,7 +40,7 @@ example/local_bucketing_client_example.py,sha256=TaIfy3VnW9fXii9NPYs5UOuP4fLKruX
38
40
  example/openfeature_example.py,sha256=kO6F-Xk20l6CPiMfIxSRreshEGmHMFHRg3zbeLXa3AI,2410
39
41
  test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
42
  test/test_cloud_client.py,sha256=zjk302m7qKMCQryolkc1WKLFmbGWUxA0VtHlZk0rY-0,11442
41
- test/test_local_client.py,sha256=kDB5-cPJGyQtVdHV4MUM-CBBeOpN-oScr6I0OuyGPmE,13022
43
+ test/test_local_client.py,sha256=GpuN9eTYKpacol7BtsB_uiviyPUxiKtxBdRcuI1m9qA,13065
42
44
  test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
45
  test/api/test_bucketing_client.py,sha256=M2mjACowLvbsIjXdP-w8-fwY02xqv1X1r_h9v-pXa7A,7739
44
46
  test/api/test_config_client.py,sha256=3BwMm4y-4NQcdW2DDGppoJLIF-ffgjlUJti2Qjw1D1M,5129
@@ -47,11 +49,11 @@ test/api/test_local_bucketing.py,sha256=0mReZ4bsFUoJWPAIcQrWXDZ9UoC5mF9MuI-9BVB6
47
49
  test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
50
  test/fixture/data.py,sha256=6ReLxWPMhCDbJ5Ex24_iR0PJNjKzPFV1I9j_v0FVbBU,1462
49
51
  test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- test/managers/test_config_manager.py,sha256=X0DxzWdjkvkUmarJSgzTDvO2Ma0TU6RNwL0WUwX2uA8,5963
52
+ test/managers/test_config_manager.py,sha256=2TUnt8501KM7UlJNH7srIhvC-izehD7nOfqZN5XgPJI,6016
51
53
  test/managers/test_event_queue_manager.py,sha256=z850atk8zAlD7sYaA1OJwgCFcfC0ps4NEk4XUnNCq1U,11629
52
54
  test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
55
  test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
54
- test/models/test_user.py,sha256=ZNTBSKLDrqrsqZuaw5AHtc_FW4empSTaeNPZHZaFlI4,6736
56
+ test/models/test_user.py,sha256=W1HaNtpuzfM2Tfm3XVMD7yDcekjtLH-b7_NZG5ovkUY,6738
55
57
  test/openfeature_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
58
  test/openfeature_test/test_provider.py,sha256=UPxAD5e7NvpIhmqNFCrTY_yrKrMyYxBc2xuMAgdSGvw,7653
57
59
  test/openfeature_test/test_provider_local_sdk.py,sha256=ad0QizKSS-lbqlIjiQvwtKHP7PCFfYam3OKHtvsFBkU,4426
@@ -59,8 +61,8 @@ test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
61
  test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
60
62
  test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
61
63
  test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
62
- devcycle_python_server_sdk-3.7.0.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
63
- devcycle_python_server_sdk-3.7.0.dist-info/METADATA,sha256=_-YyCxkWszSUSrrSonIq-r9mkx-AqA3KiCAy1MRRuec,503
64
- devcycle_python_server_sdk-3.7.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
65
- devcycle_python_server_sdk-3.7.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
66
- devcycle_python_server_sdk-3.7.0.dist-info/RECORD,,
64
+ devcycle_python_server_sdk-3.8.0.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
65
+ devcycle_python_server_sdk-3.8.0.dist-info/METADATA,sha256=3mJ9Qy7LhLeWbba7JnkxHEaCBh6LNi8K2tQpjzlKSik,684
66
+ devcycle_python_server_sdk-3.8.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
67
+ devcycle_python_server_sdk-3.8.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
68
+ devcycle_python_server_sdk-3.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -19,7 +19,9 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
19
19
  def setUp(self) -> None:
20
20
  self.sdk_key = "dvc_server_" + str(uuid.uuid4())
21
21
  self.test_local_bucketing = MagicMock()
22
- self.test_options = DevCycleLocalOptions(config_polling_interval_ms=500)
22
+ self.test_options = DevCycleLocalOptions(
23
+ config_polling_interval_ms=500, disable_realtime_updates=True
24
+ )
23
25
 
24
26
  now = datetime.now()
25
27
  stamp = mktime(now.timetuple())
test/models/test_user.py CHANGED
@@ -129,7 +129,7 @@ class DevCycleUserTest(unittest.TestCase):
129
129
  "strValue": "hello",
130
130
  "intValue": 123,
131
131
  "floatValue": 3.1456,
132
- "boolValue": True,
132
+ "boolValue": False,
133
133
  },
134
134
  )
135
135
  user = DevCycleUser.create_user_from_context(context)
@@ -143,7 +143,7 @@ class DevCycleUserTest(unittest.TestCase):
143
143
  "strValue": "hello",
144
144
  "intValue": 123,
145
145
  "floatValue": 3.1456,
146
- "boolValue": True,
146
+ "boolValue": False,
147
147
  },
148
148
  )
149
149
  self.assertEqual(user.privateCustomData, None)
test/test_local_client.py CHANGED
@@ -40,6 +40,7 @@ class DevCycleLocalClientTest(unittest.TestCase):
40
40
  config_cdn_uri="http://localhost/",
41
41
  disable_custom_event_logging=True,
42
42
  disable_automatic_event_logging=True,
43
+ disable_realtime_updates=True,
43
44
  )
44
45
  self.test_user = DevCycleUser(user_id="test_user_id")
45
46
  self.test_user_empty_id = DevCycleUser(user_id="")
@@ -1,20 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: devcycle-python-server-sdk
3
- Version: 3.7.0
4
- Summary: DevCycle Python SDK
5
- Home-page: https://github.com/devcycleHQ/python-server-sdk
6
- Author: UNKNOWN
7
- Author-email: UNKNOWN
8
- License: UNKNOWN
9
- Keywords: DevCycle
10
- Platform: UNKNOWN
11
- Requires-Dist: setuptools>=21.0.0
12
- Requires-Dist: urllib3>=1.15.1
13
- Requires-Dist: requests~=2.31
14
- Requires-Dist: wasmtime==23.0.0
15
- Requires-Dist: protobuf>=4.23.3
16
- Requires-Dist: openfeature-sdk>=0.7.0
17
-
18
- The DevCycle Python SDK used for feature management.
19
-
20
-