devcycle-python-server-sdk 3.7.0__tar.gz → 3.8.0__tar.gz

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 (76) hide show
  1. devcycle_python_server_sdk-3.8.0/PKG-INFO +25 -0
  2. devcycle_python_server_sdk-3.8.0/devcycle_python_sdk/VERSION.txt +1 -0
  3. devcycle_python_server_sdk-3.8.0/devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
  4. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/managers/config_manager.py +53 -8
  5. devcycle_python_server_sdk-3.8.0/devcycle_python_sdk/managers/sse_manager.py +63 -0
  6. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/user.py +1 -1
  7. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/options.py +8 -0
  8. devcycle_python_server_sdk-3.8.0/devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +120 -0
  9. devcycle_python_server_sdk-3.8.0/devcycle_python_server_sdk.egg-info/PKG-INFO +25 -0
  10. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_server_sdk.egg-info/SOURCES.txt +5 -0
  11. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_server_sdk.egg-info/requires.txt +3 -0
  12. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/pyproject.toml +6 -0
  13. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/managers/test_config_manager.py +3 -1
  14. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/models/test_user.py +2 -2
  15. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/test_local_client.py +1 -0
  16. devcycle_python_server_sdk-3.8.0/test/util/__init__.py +0 -0
  17. devcycle-python-server-sdk-3.7.0/PKG-INFO +0 -12
  18. devcycle-python-server-sdk-3.7.0/devcycle_python_server_sdk.egg-info/PKG-INFO +0 -12
  19. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/LICENSE +0 -0
  20. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/README.md +0 -0
  21. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/__init__.py +0 -0
  22. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/__init__.py +0 -0
  23. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/backoff.py +0 -0
  24. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/bucketing_client.py +0 -0
  25. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/config_client.py +0 -0
  26. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/event_client.py +0 -0
  27. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/api/local_bucketing.py +0 -0
  28. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/cloud_client.py +0 -0
  29. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/devcycle_client.py +0 -0
  30. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/exceptions.py +0 -0
  31. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/local_client.py +0 -0
  32. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/managers/__init__.py +0 -0
  33. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/managers/event_queue_manager.py +0 -0
  34. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/__init__.py +0 -0
  35. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/bucketed_config.py +0 -0
  36. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/error_response.py +0 -0
  37. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/event.py +0 -0
  38. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/feature.py +0 -0
  39. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/platform_data.py +0 -0
  40. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/models/variable.py +0 -0
  41. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/open_feature_provider/__init__.py +0 -0
  42. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/open_feature_provider/provider.py +0 -0
  43. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/protobuf/__init__.py +0 -0
  44. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/protobuf/utils.py +0 -0
  45. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +0 -0
  46. /devcycle-python-server-sdk-3.7.0/example/__init__.py → /devcycle_python_server_sdk-3.8.0/devcycle_python_sdk/py.typed +0 -0
  47. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/util/__init__.py +0 -0
  48. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/util/strings.py +0 -0
  49. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_sdk/util/version.py +0 -0
  50. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_server_sdk.egg-info/dependency_links.txt +0 -0
  51. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/devcycle_python_server_sdk.egg-info/top_level.txt +0 -0
  52. {devcycle-python-server-sdk-3.7.0/test → devcycle_python_server_sdk-3.8.0/example}/__init__.py +0 -0
  53. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/example/cloud_client_example.py +0 -0
  54. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/example/local_bucketing_client_example.py +0 -0
  55. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/example/openfeature_example.py +0 -0
  56. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/setup.cfg +0 -0
  57. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/setup.py +0 -0
  58. {devcycle-python-server-sdk-3.7.0/test/api → devcycle_python_server_sdk-3.8.0/test}/__init__.py +0 -0
  59. {devcycle-python-server-sdk-3.7.0/test/fixture → devcycle_python_server_sdk-3.8.0/test/api}/__init__.py +0 -0
  60. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/api/test_bucketing_client.py +0 -0
  61. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/api/test_config_client.py +0 -0
  62. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/api/test_event_client.py +0 -0
  63. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/api/test_local_bucketing.py +0 -0
  64. {devcycle-python-server-sdk-3.7.0/test/managers → devcycle_python_server_sdk-3.8.0/test/fixture}/__init__.py +0 -0
  65. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/fixture/data.py +0 -0
  66. {devcycle-python-server-sdk-3.7.0/test/models → devcycle_python_server_sdk-3.8.0/test/managers}/__init__.py +0 -0
  67. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/managers/test_event_queue_manager.py +0 -0
  68. {devcycle-python-server-sdk-3.7.0/test/openfeature_test → devcycle_python_server_sdk-3.8.0/test/models}/__init__.py +0 -0
  69. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/models/test_bucketed_config.py +0 -0
  70. {devcycle-python-server-sdk-3.7.0/test/util → devcycle_python_server_sdk-3.8.0/test/openfeature_test}/__init__.py +0 -0
  71. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/openfeature_test/test_provider.py +0 -0
  72. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/openfeature_test/test_provider_local_sdk.py +0 -0
  73. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/test_cloud_client.py +0 -0
  74. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/util/test_strings.py +0 -0
  75. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/util/test_utils.py +0 -0
  76. {devcycle-python-server-sdk-3.7.0 → devcycle_python_server_sdk-3.8.0}/test/util/test_version.py +0 -0
@@ -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,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
+
@@ -3,12 +3,15 @@ README.md
3
3
  pyproject.toml
4
4
  setup.cfg
5
5
  setup.py
6
+ devcycle_python_sdk/VERSION.txt
6
7
  devcycle_python_sdk/__init__.py
8
+ devcycle_python_sdk/bucketing-lib.release.wasm
7
9
  devcycle_python_sdk/cloud_client.py
8
10
  devcycle_python_sdk/devcycle_client.py
9
11
  devcycle_python_sdk/exceptions.py
10
12
  devcycle_python_sdk/local_client.py
11
13
  devcycle_python_sdk/options.py
14
+ devcycle_python_sdk/py.typed
12
15
  devcycle_python_sdk/api/__init__.py
13
16
  devcycle_python_sdk/api/backoff.py
14
17
  devcycle_python_sdk/api/bucketing_client.py
@@ -18,6 +21,7 @@ devcycle_python_sdk/api/local_bucketing.py
18
21
  devcycle_python_sdk/managers/__init__.py
19
22
  devcycle_python_sdk/managers/config_manager.py
20
23
  devcycle_python_sdk/managers/event_queue_manager.py
24
+ devcycle_python_sdk/managers/sse_manager.py
21
25
  devcycle_python_sdk/models/__init__.py
22
26
  devcycle_python_sdk/models/bucketed_config.py
23
27
  devcycle_python_sdk/models/error_response.py
@@ -31,6 +35,7 @@ devcycle_python_sdk/open_feature_provider/provider.py
31
35
  devcycle_python_sdk/protobuf/__init__.py
32
36
  devcycle_python_sdk/protobuf/utils.py
33
37
  devcycle_python_sdk/protobuf/variableForUserParams_pb2.py
38
+ devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi
34
39
  devcycle_python_sdk/util/__init__.py
35
40
  devcycle_python_sdk/util/strings.py
36
41
  devcycle_python_sdk/util/version.py
@@ -4,3 +4,6 @@ requests~=2.31
4
4
  wasmtime==23.0.0
5
5
  protobuf>=4.23.3
6
6
  openfeature-sdk>=0.7.0
7
+ launchdarkly-eventsource>=1.2.0
8
+ responses~=0.23.1
9
+ dataclasses~=0.6
@@ -28,6 +28,12 @@ ignore_missing_imports = true
28
28
  module = 'test.openfeature.*'
29
29
  ignore_errors = true
30
30
 
31
+ [[tool.mypy.overrides]]
32
+ module = 'ld_eventsource.*'
33
+ ignore_errors = true
34
+ ignore_missing_imports = true
35
+
36
+
31
37
  [[tool.mypy.overrides]]
32
38
  module = 'openfeature.*'
33
39
  ignore_errors = true
@@ -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())
@@ -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)
@@ -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="")
File without changes
@@ -1,12 +0,0 @@
1
- Metadata-Version: 1.0
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
- Description: The DevCycle Python SDK used for feature management.
10
-
11
- Keywords: DevCycle
12
- Platform: UNKNOWN
@@ -1,12 +0,0 @@
1
- Metadata-Version: 1.0
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
- Description: The DevCycle Python SDK used for feature management.
10
-
11
- Keywords: DevCycle
12
- Platform: UNKNOWN