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.
- devcycle_python_sdk/VERSION.txt +1 -1
- devcycle_python_sdk/managers/config_manager.py +53 -8
- devcycle_python_sdk/managers/sse_manager.py +63 -0
- devcycle_python_sdk/models/user.py +1 -1
- devcycle_python_sdk/options.py +8 -0
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi +120 -0
- devcycle_python_server_sdk-3.8.0.dist-info/METADATA +25 -0
- {devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/RECORD +14 -12
- {devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/WHEEL +1 -1
- test/managers/test_config_manager.py +3 -1
- test/models/test_user.py +2 -2
- test/test_local_client.py +1 -0
- devcycle_python_server_sdk-3.7.0.dist-info/METADATA +0 -20
- {devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/LICENSE +0 -0
- {devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/top_level.txt +0 -0
devcycle_python_sdk/VERSION.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.8.0
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
1
3
|
import threading
|
|
2
4
|
import time
|
|
3
|
-
import
|
|
4
|
-
import json
|
|
5
|
+
from datetime import datetime
|
|
5
6
|
from typing import Optional
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
devcycle_python_sdk/options.py
CHANGED
|
@@ -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
|
+
|
{devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/RECORD
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
devcycle_python_sdk/VERSION.txt,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
63
|
-
devcycle_python_server_sdk-3.
|
|
64
|
-
devcycle_python_server_sdk-3.
|
|
65
|
-
devcycle_python_server_sdk-3.
|
|
66
|
-
devcycle_python_server_sdk-3.
|
|
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,,
|
|
@@ -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(
|
|
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":
|
|
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":
|
|
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
|
-
|
{devcycle_python_server_sdk-3.7.0.dist-info → devcycle_python_server_sdk-3.8.0.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|