devcycle-python-server-sdk 3.10.0__py3-none-any.whl → 3.11.1__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.10.0
1
+ 3.11.1
@@ -182,6 +182,13 @@ class DevCycleCloudClient(AbstractDevCycleClient):
182
182
  except Exception as e:
183
183
  logger.error(f"DevCycle: Error tracking event: {e}")
184
184
 
185
+ def close(self) -> None:
186
+ """
187
+ Closes the client and releases any resources held by it.
188
+ """
189
+ # Cloud client doesn't need to release any resources
190
+ logger.debug("DevCycle: Cloud client closed")
191
+
185
192
 
186
193
  def _validate_sdk_key(sdk_key: str) -> None:
187
194
  if sdk_key is None or len(sdk_key) == 0:
@@ -38,3 +38,10 @@ class AbstractDevCycleClient:
38
38
  @abstractmethod
39
39
  def get_sdk_platform(self) -> str:
40
40
  pass
41
+
42
+ @abstractmethod
43
+ def close(self) -> None:
44
+ """
45
+ Closes the client and releases any resources held by it.
46
+ """
47
+ pass
@@ -2,7 +2,7 @@ import json
2
2
  import logging
3
3
  import uuid
4
4
  from numbers import Real
5
- from typing import Any, Dict, Union
5
+ from typing import Any, Dict, Union, Optional
6
6
 
7
7
  from devcycle_python_sdk import DevCycleLocalOptions, AbstractDevCycleClient
8
8
  from devcycle_python_sdk.api.local_bucketing import LocalBucketing
@@ -50,12 +50,21 @@ class DevCycleLocalClient(AbstractDevCycleClient):
50
50
  sdk_key, self.client_uuid, self.options, self.local_bucketing
51
51
  )
52
52
 
53
- self._openfeature_provider = DevCycleProvider(self)
53
+ self._openfeature_provider: Optional[DevCycleProvider] = None
54
54
 
55
55
  def get_sdk_platform(self) -> str:
56
56
  return "Local"
57
57
 
58
58
  def get_openfeature_provider(self) -> AbstractProvider:
59
+ if self._openfeature_provider is None:
60
+ self._openfeature_provider = DevCycleProvider(self)
61
+
62
+ # Update platform data for OpenFeature
63
+ self._platform_data.sdkPlatform = "python-of"
64
+ self.local_bucketing.set_platform_data(
65
+ json.dumps(self._platform_data.to_json())
66
+ )
67
+
59
68
  return self._openfeature_provider
60
69
 
61
70
  def is_initialized(self) -> bool:
@@ -2,42 +2,48 @@ import threading
2
2
 
3
3
  import ld_eventsource
4
4
  import ld_eventsource.actions
5
+ import logging
5
6
  import ld_eventsource.config
6
7
  from typing import Callable
7
8
 
9
+ logger = logging.getLogger(__name__)
10
+
8
11
 
9
12
  class SSEManager:
10
13
  def __init__(
11
14
  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
+ handle_state: Callable[[ld_eventsource.actions.Start], None],
16
+ handle_error: Callable[[ld_eventsource.actions.Fault], None],
17
+ handle_message: Callable[[ld_eventsource.actions.Event], None],
15
18
  ):
16
19
  self.client: ld_eventsource.SSEClient = None
17
20
  self.url = ""
18
- self.handlestate = handlestate
19
- self.handleerror = handleerror
20
- self.handlemessage = handlemessage
21
+ self.handle_state = handle_state
22
+ self.handle_error = handle_error
23
+ self.handle_message = handle_message
21
24
 
22
25
  self.read_thread = threading.Thread(
23
26
  target=self.read_events,
24
- args=(self.handlestate, self.handleerror, self.handlemessage),
27
+ args=(self.handle_state, self.handle_error, self.handle_message),
25
28
  )
26
29
 
27
30
  def read_events(
28
31
  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
+ handle_state: Callable[[ld_eventsource.actions.Start], None],
33
+ handle_error: Callable[[ld_eventsource.actions.Fault], None],
34
+ handle_message: Callable[[ld_eventsource.actions.Event], None],
32
35
  ):
33
36
  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)
37
+ try:
38
+ for event in self.client.all:
39
+ if isinstance(event, ld_eventsource.actions.Start):
40
+ handle_state(event)
41
+ elif isinstance(event, ld_eventsource.actions.Fault):
42
+ handle_error(event)
43
+ elif isinstance(event, ld_eventsource.actions.Event):
44
+ handle_message(event)
45
+ except Exception as e:
46
+ logger.exception(f"DevCycle: failed to read SSE message: {e}")
41
47
 
42
48
  def update(self, config: dict):
43
49
  if self.use_new_config(config["sse"]):
@@ -52,7 +58,7 @@ class SSEManager:
52
58
  )
53
59
  self.read_thread = threading.Thread(
54
60
  target=self.read_events,
55
- args=(self.handlestate, self.handleerror, self.handlemessage),
61
+ args=(self.handle_state, self.handle_error, self.handle_message),
56
62
  )
57
63
  self.read_thread.start()
58
64
 
@@ -18,7 +18,7 @@ class EventType:
18
18
  class DevCycleEvent:
19
19
  type: Optional[str] = None
20
20
  target: Optional[str] = None
21
- date: datetime = field(default_factory=lambda: datetime.utcnow())
21
+ date: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
22
22
  value: Optional[int] = None
23
23
  metaData: Optional[Dict[str, str]] = None
24
24
 
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: N815
2
2
  import platform
3
3
  import socket
4
+ from typing import Optional
4
5
  from dataclasses import dataclass
5
6
  from devcycle_python_sdk.util.version import sdk_version
6
7
 
@@ -13,6 +14,7 @@ class PlatformData:
13
14
  deviceModel: str
14
15
  platform: str
15
16
  hostname: str
17
+ sdkPlatform: Optional[str] = None
16
18
 
17
19
  def to_json(self):
18
20
  return {
@@ -16,7 +16,7 @@ class DevCycleUser:
16
16
  appVersion: Optional[str] = None
17
17
  appBuild: Optional[str] = None
18
18
  customData: Optional[Dict[str, Any]] = None
19
- createdDate: datetime = field(default_factory=lambda: datetime.utcnow())
19
+ createdDate: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
20
20
  privateCustomData: Optional[Dict[str, Any]] = None
21
21
  lastSeenDate: Optional[datetime] = None
22
22
  platform: Optional[str] = None
@@ -24,6 +24,7 @@ class DevCycleUser:
24
24
  deviceModel: Optional[str] = None
25
25
  sdkType: Optional[str] = None
26
26
  sdkVersion: Optional[str] = None
27
+ sdkPlatform: Optional[str] = None
27
28
 
28
29
  def to_json(self):
29
30
  json_dict = {
@@ -50,7 +51,7 @@ class DevCycleUser:
50
51
  data["createdDate"].replace("Z", "+00:00")
51
52
  )
52
53
  else:
53
- created_date = datetime.utcnow()
54
+ created_date = datetime.now(timezone.utc)
54
55
 
55
56
  last_seen_date = None
56
57
  if "lastSeenDate" in data:
@@ -75,6 +76,7 @@ class DevCycleUser:
75
76
  deviceModel=data.get("deviceModel"),
76
77
  sdkType=data.get("sdkType"),
77
78
  sdkVersion=data.get("sdkVersion"),
79
+ sdkPlatform=data.get("sdkPlatform"),
78
80
  )
79
81
 
80
82
  @staticmethod
@@ -156,4 +158,5 @@ class DevCycleUser:
156
158
  if private_custom_data:
157
159
  user.privateCustomData = private_custom_data
158
160
 
161
+ user.sdkPlatform = "python-of"
159
162
  return user
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import time
2
3
 
3
4
  from typing import Any, Optional, Union, List
4
5
 
@@ -6,11 +7,16 @@ from devcycle_python_sdk import AbstractDevCycleClient
6
7
  from devcycle_python_sdk.models.user import DevCycleUser
7
8
 
8
9
  from openfeature.provider import AbstractProvider
10
+ from openfeature.provider.metadata import Metadata
9
11
  from openfeature.evaluation_context import EvaluationContext
10
12
  from openfeature.flag_evaluation import FlagResolutionDetails, Reason
11
- from openfeature.exception import ErrorCode, InvalidContextError, TypeMismatchError
13
+ from openfeature.exception import (
14
+ ErrorCode,
15
+ InvalidContextError,
16
+ TypeMismatchError,
17
+ GeneralError,
18
+ )
12
19
  from openfeature.hook import Hook
13
- from openfeature.provider.metadata import Metadata
14
20
 
15
21
  logger = logging.getLogger(__name__)
16
22
 
@@ -26,6 +32,24 @@ class DevCycleProvider(AbstractProvider):
26
32
  self.client = devcycle_client
27
33
  self.meta_data = Metadata(name=f"DevCycle {self.client.get_sdk_platform()}")
28
34
 
35
+ def initialize(self, evaluation_context: EvaluationContext) -> None:
36
+ timeout = 2
37
+ start_time = time.time()
38
+
39
+ # Wait for the client to be initialized or timeout
40
+ while not self.client.is_initialized():
41
+ if time.time() - start_time > timeout:
42
+ raise GeneralError(
43
+ f"DevCycleProvider initialization timed out after {timeout} seconds"
44
+ )
45
+ time.sleep(0.1) # Sleep briefly to avoid busy waiting
46
+
47
+ if self.client.is_initialized():
48
+ logger.debug("DevCycleProvider initialized successfully")
49
+
50
+ def shutdown(self) -> None:
51
+ self.client.close()
52
+
29
53
  def get_metadata(self) -> Metadata:
30
54
  return self.meta_data
31
55
 
@@ -1,23 +1,24 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: devcycle-python-server-sdk
3
- Version: 3.10.0
3
+ Version: 3.11.1
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
- Requires-Python: >=3.8
8
+ Requires-Python: >=3.9
9
9
  License-File: LICENSE
10
10
  Requires-Dist: setuptools>=21.0.0
11
11
  Requires-Dist: urllib3>=1.15.1
12
- Requires-Dist: requests~=2.31
13
- Requires-Dist: wasmtime==23.0.0
12
+ Requires-Dist: requests>=2.32
13
+ Requires-Dist: wasmtime~=30.0.0
14
14
  Requires-Dist: protobuf>=4.23.3
15
- Requires-Dist: openfeature-sdk>=0.7.0
16
- Requires-Dist: launchdarkly-eventsource>=1.2.0
17
- Requires-Dist: responses~=0.23.1
15
+ Requires-Dist: openfeature-sdk>=0.8.0
16
+ Requires-Dist: launchdarkly-eventsource>=1.2.1
17
+ Requires-Dist: responses>=0.23.1
18
18
  Dynamic: description
19
19
  Dynamic: home-page
20
20
  Dynamic: keywords
21
+ Dynamic: license-file
21
22
  Dynamic: requires-dist
22
23
  Dynamic: requires-python
23
24
  Dynamic: summary
@@ -1,10 +1,10 @@
1
- devcycle_python_sdk/VERSION.txt,sha256=rGeoqWHOE1NzOu73didX8_OyjR9O8b-XNp87WjKbO0s,7
1
+ devcycle_python_sdk/VERSION.txt,sha256=jPDPwh5YS8ds4NdxZRfusNwEmLpXwF0ChUN0jVEVXAk,7
2
2
  devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
3
- devcycle_python_sdk/bucketing-lib.release.wasm,sha256=AJyYTuQs-PRjAiH9yzSgn-iESa9W7Vu0AQHLJHC5QS0,214842
4
- devcycle_python_sdk/cloud_client.py,sha256=UMfXvXwO4v3pZW9ZA52ivxPH1Lp4Goy5gpV_s0qCpxQ,7352
5
- devcycle_python_sdk/devcycle_client.py,sha256=W5MeOslkXjTqZtoSy_Bbbug8gVoicjxshpKjDteEr6E,917
3
+ devcycle_python_sdk/bucketing-lib.release.wasm,sha256=Kuw8UrGafeyB8Z1wyBgGQpuXIhP4NvPjXZcVQiNZzl8,215020
4
+ devcycle_python_sdk/cloud_client.py,sha256=VVV3O8Fbrp3LrhKp0BNCF-pMqOuCA6TEEABBmjpfpOY,7586
5
+ devcycle_python_sdk/devcycle_client.py,sha256=OqNza7OXOSRmMQq2kmyivljmH1GyN4dTiz0EhvDvt8w,1069
6
6
  devcycle_python_sdk/exceptions.py,sha256=Et7fS8pEFbHkFY86T2pJoEUstwjh4_rzhJxVH8fzAns,1053
7
- devcycle_python_sdk/local_client.py,sha256=WeXt-gWhFsaPjAp2UEmygQhzx_ygx2Jaq0z0yRHaah0,9448
7
+ devcycle_python_sdk/local_client.py,sha256=cC9N144DY-WWatcSwVQh-e_07mnncjC3NG88Eo9wX0g,9814
8
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
@@ -16,17 +16,17 @@ devcycle_python_sdk/api/local_bucketing.py,sha256=_MAMCHviIS1n4GiU-CnnLBeEVX8BUF
16
16
  devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  devcycle_python_sdk/managers/config_manager.py,sha256=JYRLAJTLDWEjeZk7Af7rxpw1t6SR-ei81S8MEc36eOU,5636
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
+ devcycle_python_sdk/managers/sse_manager.py,sha256=PElIsmmjUvLDHABikLxUq6CVjIJDnmqkY3psPILCTS8,2590
20
20
  devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  devcycle_python_sdk/models/bucketed_config.py,sha256=NsdT-6RbEuDy5QuK5qlr6UqNKQQnwjoXVYN-uQp0P-E,3999
22
22
  devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
23
- devcycle_python_sdk/models/event.py,sha256=-6bhKp72mAgaoSxvhS__G4vvfQ9Psj83fc0Igelzw3o,3713
23
+ devcycle_python_sdk/models/event.py,sha256=Sj3eEopuPp8QwQl_Oz8E6Y9Ph6nLG0HxU7eR7xSbA7g,3722
24
24
  devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
25
- devcycle_python_sdk/models/platform_data.py,sha256=kIFevH5trsxI32qp7BW6glUCpipgpaVBKGqJWzQd5Xo,783
26
- devcycle_python_sdk/models/user.py,sha256=P2yRqorHosN6ho6lB8eTtz2zY0r4NnU3t8NuLU4nllw,6278
25
+ devcycle_python_sdk/models/platform_data.py,sha256=nyQsgI6pcTd1FL3Y2ACmpvTYb08R_UtWul25OWZstps,849
26
+ devcycle_python_sdk/models/user.py,sha256=-tt-LQV-sBxNNfAKAlbNkHpsS01Gi0w37ZJQDARtve8,6422
27
27
  devcycle_python_sdk/models/variable.py,sha256=qp00SyCNyfYiMlTwnDSDJ4ZetRF35JHTbYZQOBxFIqY,1711
28
28
  devcycle_python_sdk/open_feature_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- devcycle_python_sdk/open_feature_provider/provider.py,sha256=VjUMhCYiMLixXUSSIjQ7kVCQrls1QD_Nltooe5EjGks,4539
29
+ devcycle_python_sdk/open_feature_provider/provider.py,sha256=nLqyW4n9_NrJqgaz6cUt3YHDoZQzorjTYPiyT4UjFdc,5255
30
30
  devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  devcycle_python_sdk/protobuf/utils.py,sha256=zi-kVx6yvgS2c04cUobLd2IDc_ZqKkWduay9YcpjoGc,4684
32
32
  devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=Zf4-8n67UnulC0Sz90aqLYsXCFEgywOqfiSHuXZomLk,4244
@@ -34,6 +34,7 @@ devcycle_python_sdk/protobuf/variableForUserParams_pb2.pyi,sha256=gjTD1KkBe5NaXi
34
34
  devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
35
35
  devcycle_python_sdk/util/strings.py,sha256=gH0Xm3gIXNqn6hczKPk10Jn5_9knuD7OB5MrB7bqztw,252
36
36
  devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
37
+ devcycle_python_server_sdk-3.11.1.dist-info/licenses/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
37
38
  example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  example/cloud_client_example.py,sha256=YD8ZsTv3dPYfbFUKraRpRT0VsJGrPwjm8ZCuA5H54yI,2463
39
40
  example/local_bucketing_client_example.py,sha256=TaIfy3VnW9fXii9NPYs5UOuP4fLKruXMjnZApveqzl4,2796
@@ -61,8 +62,7 @@ test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
62
  test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
62
63
  test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
63
64
  test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
64
- devcycle_python_server_sdk-3.10.0.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
65
- devcycle_python_server_sdk-3.10.0.dist-info/METADATA,sha256=Oacz012hbdOr1qxhfhqTwYYrf5kkJRaNw_c80vpYN9Q,701
66
- devcycle_python_server_sdk-3.10.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
67
- devcycle_python_server_sdk-3.10.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
68
- devcycle_python_server_sdk-3.10.0.dist-info/RECORD,,
65
+ devcycle_python_server_sdk-3.11.1.dist-info/METADATA,sha256=UGoYroHYqu585KRz63PgJe-5oogDuwQ7u5Q9jArLcMM,723
66
+ devcycle_python_server_sdk-3.11.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
67
+ devcycle_python_server_sdk-3.11.1.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
68
+ devcycle_python_server_sdk-3.11.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5