devcycle-python-server-sdk 3.3.5__py3-none-any.whl → 3.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. devcycle_python_sdk/VERSION.txt +1 -1
  2. devcycle_python_sdk/__init__.py +1 -0
  3. devcycle_python_sdk/api/bucketing_client.py +2 -2
  4. devcycle_python_sdk/api/config_client.py +4 -2
  5. devcycle_python_sdk/api/event_client.py +2 -2
  6. devcycle_python_sdk/bucketing-lib.release.wasm +0 -0
  7. devcycle_python_sdk/cloud_client.py +19 -2
  8. devcycle_python_sdk/devcycle_client.py +40 -0
  9. devcycle_python_sdk/local_client.py +16 -2
  10. devcycle_python_sdk/models/bucketed_config.py +2 -0
  11. devcycle_python_sdk/models/user.py +86 -3
  12. devcycle_python_sdk/open_feature_provider/__init__.py +0 -0
  13. devcycle_python_sdk/open_feature_provider/provider.py +123 -0
  14. devcycle_python_sdk/util/strings.py +6 -0
  15. {devcycle_python_server_sdk-3.3.5.dist-info → devcycle_python_server_sdk-3.5.0.dist-info}/METADATA +2 -1
  16. {devcycle_python_server_sdk-3.3.5.dist-info → devcycle_python_server_sdk-3.5.0.dist-info}/RECORD +33 -23
  17. {devcycle_python_server_sdk-3.3.5.dist-info → devcycle_python_server_sdk-3.5.0.dist-info}/WHEEL +1 -1
  18. example/local_bucketing_client_example.py +1 -1
  19. example/openfeature_example.py +77 -0
  20. test/api/test_bucketing_client.py +5 -1
  21. test/api/test_config_client.py +5 -2
  22. test/api/test_event_client.py +3 -0
  23. test/api/test_local_bucketing.py +15 -12
  24. test/fixture/data.py +5 -5
  25. test/models/test_user.py +188 -0
  26. test/openfeature_test/__init__.py +0 -0
  27. test/openfeature_test/test_provider.py +219 -0
  28. test/openfeature_test/test_provider_local_sdk.py +136 -0
  29. test/test_local_client.py +10 -0
  30. test/util/test_strings.py +25 -0
  31. test/util/test_version.py +1 -1
  32. {devcycle_python_server_sdk-3.3.5.dist-info → devcycle_python_server_sdk-3.5.0.dist-info}/LICENSE +0 -0
  33. {devcycle_python_server_sdk-3.3.5.dist-info → devcycle_python_server_sdk-3.5.0.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- 3.3.5
1
+ 3.5.0
@@ -1,5 +1,6 @@
1
1
  # Simplify imports for the SDK entry point objects
2
2
 
3
3
  from devcycle_python_sdk.options import DevCycleCloudOptions, DevCycleLocalOptions
4
+ from devcycle_python_sdk.devcycle_client import AbstractDevCycleClient
4
5
  from devcycle_python_sdk.cloud_client import DevCycleCloudClient
5
6
  from devcycle_python_sdk.local_client import DevCycleLocalClient
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
  import time
3
- from os.path import join
4
3
  from typing import Dict, List, Optional
5
4
 
6
5
  import requests
@@ -16,6 +15,7 @@ from devcycle_python_sdk.models.event import DevCycleEvent
16
15
  from devcycle_python_sdk.models.feature import Feature
17
16
  from devcycle_python_sdk.models.user import DevCycleUser
18
17
  from devcycle_python_sdk.models.variable import Variable
18
+ from devcycle_python_sdk.util.strings import slash_join
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
@@ -33,7 +33,7 @@ class BucketingAPIClient:
33
33
  self.session.max_redirects = 0
34
34
 
35
35
  def _url(self, *path_args: str) -> str:
36
- return join(self.options.bucketing_api_uri, "v1", *path_args)
36
+ return slash_join(self.options.bucketing_api_uri, "v1", *path_args)
37
37
 
38
38
  def request(self, method: str, url: str, **kwargs) -> dict:
39
39
  retries_remaining = self.options.request_retries + 1
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
  import time
3
3
  from http import HTTPStatus
4
- from os.path import join
5
4
  from typing import Optional, Tuple
6
5
 
7
6
  import requests
@@ -13,6 +12,7 @@ from devcycle_python_sdk.exceptions import (
13
12
  NotFoundError,
14
13
  APIClientUnauthorizedError,
15
14
  )
15
+ from devcycle_python_sdk.util.strings import slash_join
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -29,7 +29,9 @@ class ConfigAPIClient:
29
29
  self.session.max_redirects = 0
30
30
  self.max_config_retries = 2
31
31
  self.config_file_url = (
32
- join(self.options.config_cdn_uri, "config", "v1", "server", self.sdk_key)
32
+ slash_join(
33
+ self.options.config_cdn_uri, "config", "v1", "server", self.sdk_key
34
+ )
33
35
  + ".json"
34
36
  )
35
37
 
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import logging
3
3
  import time
4
- from os.path import join
5
4
  from typing import Optional, List
6
5
 
7
6
  import requests
@@ -14,6 +13,7 @@ from devcycle_python_sdk.exceptions import (
14
13
  APIClientUnauthorizedError,
15
14
  )
16
15
  from devcycle_python_sdk.models.event import UserEventsBatchRecord
16
+ from devcycle_python_sdk.util.strings import slash_join
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -29,7 +29,7 @@ class EventAPIClient:
29
29
  }
30
30
  self.session.max_redirects = 0
31
31
  self.max_batch_retries = 0 # we don't retry events batches
32
- self.batch_url = join(self.options.events_api_uri, "v1/events/batch")
32
+ self.batch_url = slash_join(self.options.events_api_uri, "v1/events/batch")
33
33
 
34
34
  def publish_events(self, batch: List[UserEventsBatchRecord]) -> str:
35
35
  """
@@ -3,7 +3,7 @@ import platform
3
3
 
4
4
  from typing import Any, Dict
5
5
 
6
- from devcycle_python_sdk import DevCycleCloudOptions
6
+ from devcycle_python_sdk import DevCycleCloudOptions, AbstractDevCycleClient
7
7
  from devcycle_python_sdk.api.bucketing_client import BucketingAPIClient
8
8
  from devcycle_python_sdk.exceptions import (
9
9
  NotFoundError,
@@ -14,11 +14,18 @@ from devcycle_python_sdk.models.event import DevCycleEvent
14
14
  from devcycle_python_sdk.models.variable import Variable
15
15
  from devcycle_python_sdk.models.feature import Feature
16
16
  from devcycle_python_sdk.util.version import sdk_version
17
+ from devcycle_python_sdk.open_feature_provider.provider import DevCycleProvider
18
+
19
+ from openfeature.provider import AbstractProvider
17
20
 
18
21
  logger = logging.getLogger(__name__)
19
22
 
20
23
 
21
- class DevCycleCloudClient:
24
+ class DevCycleCloudClient(AbstractDevCycleClient):
25
+ """
26
+ The DevCycle Python SDK that utilizes the DevCycle Bucketing API for feature and variable evaluation
27
+ """
28
+
22
29
  options: DevCycleCloudOptions
23
30
  platform: str
24
31
  platform_version: str
@@ -37,6 +44,13 @@ class DevCycleCloudClient:
37
44
  self.sdk_version = sdk_version()
38
45
  self.sdk_type = "server"
39
46
  self.bucketing_api = BucketingAPIClient(sdk_key, self.options)
47
+ self._openfeature_provider = DevCycleProvider(self)
48
+
49
+ def get_sdk_platform(self) -> str:
50
+ return "Cloud"
51
+
52
+ def get_openfeature_provider(self) -> AbstractProvider:
53
+ return self._openfeature_provider
40
54
 
41
55
  def _add_platform_data_to_user(self, user: DevCycleUser) -> DevCycleUser:
42
56
  user.platform = self.platform
@@ -45,6 +59,9 @@ class DevCycleCloudClient:
45
59
  user.sdkType = self.sdk_type
46
60
  return user
47
61
 
62
+ def is_initialized(self) -> bool:
63
+ return True
64
+
48
65
  def variable_value(self, user: DevCycleUser, key: str, default_value: Any) -> Any:
49
66
  """
50
67
  Evaluates a variable for a user and returns the value. If the user is not bucketed into the variable, the default value will be returned
@@ -0,0 +1,40 @@
1
+ import typing
2
+ from abc import abstractmethod
3
+
4
+ from devcycle_python_sdk.models.user import DevCycleUser
5
+ from devcycle_python_sdk.models.variable import Variable
6
+
7
+ from openfeature.provider import AbstractProvider
8
+
9
+
10
+ class AbstractDevCycleClient:
11
+ """
12
+ A common interface for all DevCycle Clients
13
+ """
14
+
15
+ @abstractmethod
16
+ def is_initialized(self) -> bool:
17
+ pass
18
+
19
+ @abstractmethod
20
+ def variable(
21
+ self, user: DevCycleUser, key: str, default_value: typing.Any
22
+ ) -> Variable:
23
+ pass
24
+
25
+ @abstractmethod
26
+ def variable_value(
27
+ self, user: DevCycleUser, key: str, default_value: typing.Any
28
+ ) -> typing.Any:
29
+ pass
30
+
31
+ @abstractmethod
32
+ def get_openfeature_provider(self) -> AbstractProvider:
33
+ """
34
+ Returns the OpenFeature provider for this client
35
+ """
36
+ pass
37
+
38
+ @abstractmethod
39
+ def get_sdk_platform(self) -> str:
40
+ pass
@@ -3,7 +3,7 @@ import logging
3
3
  from numbers import Real
4
4
  from typing import Any, Dict, Union
5
5
 
6
- from devcycle_python_sdk import DevCycleLocalOptions
6
+ from devcycle_python_sdk import DevCycleLocalOptions, AbstractDevCycleClient
7
7
  from devcycle_python_sdk.api.local_bucketing import LocalBucketing
8
8
  from devcycle_python_sdk.exceptions import VariableTypeMismatchError
9
9
  from devcycle_python_sdk.managers.config_manager import EnvironmentConfigManager
@@ -14,11 +14,17 @@ from devcycle_python_sdk.models.feature import Feature
14
14
  from devcycle_python_sdk.models.platform_data import default_platform_data
15
15
  from devcycle_python_sdk.models.user import DevCycleUser
16
16
  from devcycle_python_sdk.models.variable import Variable
17
+ from devcycle_python_sdk.open_feature_provider.provider import DevCycleProvider
18
+ from openfeature.provider import AbstractProvider
17
19
 
18
20
  logger = logging.getLogger(__name__)
19
21
 
20
22
 
21
- class DevCycleLocalClient:
23
+ class DevCycleLocalClient(AbstractDevCycleClient):
24
+ """
25
+ The DevCycle Python SDK that utilizes the local bucketing library for feature and variable evaluation
26
+ """
27
+
22
28
  def __init__(self, sdk_key: str, options: DevCycleLocalOptions):
23
29
  _validate_sdk_key(sdk_key)
24
30
  self._sdk_key = sdk_key
@@ -42,6 +48,14 @@ class DevCycleLocalClient:
42
48
  sdk_key, self.options, self.local_bucketing
43
49
  )
44
50
 
51
+ self._openfeature_provider = DevCycleProvider(self)
52
+
53
+ def get_sdk_platform(self) -> str:
54
+ return "Local"
55
+
56
+ def get_openfeature_provider(self) -> AbstractProvider:
57
+ return self._openfeature_provider
58
+
45
59
  def is_initialized(self) -> bool:
46
60
  return self.config_manager.is_initialized()
47
61
 
@@ -55,6 +55,7 @@ class OptInSettings:
55
55
  class ProjectSettings:
56
56
  edge_db: Optional[EdgeDBSettings]
57
57
  opt_in: Optional[OptInSettings]
58
+ disable_passthrough_rollouts: Optional[bool]
58
59
 
59
60
  @classmethod
60
61
  def from_json(cls, data: dict) -> "ProjectSettings":
@@ -63,6 +64,7 @@ class ProjectSettings:
63
64
  if "edgeDB" in data
64
65
  else None,
65
66
  opt_in=OptInSettings.from_json(data["optIn"]) if "optIn" in data else None,
67
+ disable_passthrough_rollouts=data.get("disablePassthroughRollouts", False),
66
68
  )
67
69
 
68
70
 
@@ -1,7 +1,9 @@
1
1
  # ruff: noqa: N815
2
2
  from dataclasses import dataclass, field
3
3
  from datetime import datetime, timezone
4
- from typing import Dict, Optional
4
+ from typing import Dict, Optional, Any
5
+ from openfeature.evaluation_context import EvaluationContext
6
+ from openfeature.exception import TargetingKeyMissingError, InvalidContextError
5
7
 
6
8
 
7
9
  @dataclass(order=False)
@@ -13,9 +15,9 @@ class DevCycleUser:
13
15
  country: Optional[str] = None
14
16
  appVersion: Optional[str] = None
15
17
  appBuild: Optional[str] = None
16
- customData: Optional[Dict[str, str]] = None
18
+ customData: Optional[Dict[str, Any]] = None
17
19
  createdDate: datetime = field(default_factory=lambda: datetime.utcnow())
18
- privateCustomData: Optional[Dict[str, str]] = None
20
+ privateCustomData: Optional[Dict[str, Any]] = None
19
21
  lastSeenDate: Optional[datetime] = None
20
22
  platform: Optional[str] = None
21
23
  platformVersion: Optional[str] = None
@@ -74,3 +76,84 @@ class DevCycleUser:
74
76
  sdkType=data.get("sdkType"),
75
77
  sdkVersion=data.get("sdkVersion"),
76
78
  )
79
+
80
+ @staticmethod
81
+ def _set_custom_value(custom_data: Dict[str, Any], key: str, value: Optional[Any]):
82
+ """
83
+ Sets a custom value in the custom data dictionary. Custom data properties can
84
+ only be strings, numbers, or booleans. Nested dictionaries and lists are
85
+ not permitted.
86
+
87
+ Invalid values will generate an error
88
+ """
89
+ if key and (value is None or isinstance(value, (str, int, float, bool))):
90
+ custom_data[key] = value
91
+ else:
92
+ raise InvalidContextError(
93
+ "Custom property values must be strings, numbers, booleans or None"
94
+ )
95
+
96
+ @staticmethod
97
+ def create_user_from_context(
98
+ context: Optional[EvaluationContext],
99
+ ) -> "DevCycleUser":
100
+ """
101
+ Builds a DevCycleUser instance from the evaluation context. Will raise a TargetingKeyMissingError if
102
+ the context does not contain a valid targeting key or user_id attribute
103
+
104
+ :param context: The evaluation context to build the user from
105
+ :return: A DevCycleUser instance
106
+ """
107
+ user_id = None
108
+
109
+ if context:
110
+ if context.targeting_key:
111
+ user_id = context.targeting_key
112
+ elif context.attributes and "user_id" in context.attributes.keys():
113
+ user_id = context.attributes["user_id"]
114
+
115
+ if not user_id or not isinstance(user_id, str):
116
+ raise TargetingKeyMissingError(
117
+ "DevCycle: Evaluation context does not contain a valid targeting key or user_id attribute"
118
+ )
119
+
120
+ user = DevCycleUser(user_id=user_id)
121
+ custom_data: Dict[str, Any] = {}
122
+ private_custom_data: Dict[str, Any] = {}
123
+ if context and context.attributes:
124
+ for key, value in context.attributes.items():
125
+ if key == "user_id":
126
+ continue
127
+
128
+ if value:
129
+ if key == "email" and isinstance(value, str):
130
+ user.email = value
131
+ elif key == "name" and isinstance(value, str):
132
+ user.name = value
133
+ elif key == "language" and isinstance(value, str):
134
+ user.language = value
135
+ elif key == "country" and isinstance(value, str):
136
+ user.country = value
137
+ elif key == "appVersion" and isinstance(value, str):
138
+ user.appVersion = value
139
+ elif key == "appBuild" and isinstance(value, str):
140
+ user.appBuild = value
141
+ elif key == "deviceModel" and isinstance(value, str):
142
+ user.deviceModel = value
143
+ elif key == "customData" and isinstance(value, dict):
144
+ for k, v in value.items():
145
+ DevCycleUser._set_custom_value(custom_data, k, v)
146
+ elif key == "privateCustomData" and isinstance(value, dict):
147
+ for k, v in value.items():
148
+ DevCycleUser._set_custom_value(private_custom_data, k, v)
149
+ else:
150
+ # unrecognized keys are just added to public custom data
151
+ DevCycleUser._set_custom_value(custom_data, key, value)
152
+
153
+ if custom_data:
154
+ user.customData = custom_data
155
+
156
+ if private_custom_data:
157
+ user.privateCustomData = private_custom_data
158
+
159
+ return user
File without changes
@@ -0,0 +1,123 @@
1
+ import logging
2
+
3
+ from typing import Any, Optional, Union, List
4
+
5
+ from devcycle_python_sdk import AbstractDevCycleClient
6
+ from devcycle_python_sdk.models.user import DevCycleUser
7
+
8
+ from openfeature.provider import AbstractProvider
9
+ from openfeature.evaluation_context import EvaluationContext
10
+ from openfeature.flag_evaluation import FlagResolutionDetails, Reason
11
+ from openfeature.exception import ErrorCode, InvalidContextError, TypeMismatchError
12
+ from openfeature.hook import Hook
13
+ from openfeature.provider.metadata import Metadata
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class DevCycleProvider(AbstractProvider):
19
+ """
20
+ Openfeature provider wrapper for the DevCycle SDK.
21
+
22
+ Can be initialized with either a DevCycleLocalClient or DevCycleCloudClient instance.
23
+ """
24
+
25
+ def __init__(self, devcycle_client: AbstractDevCycleClient):
26
+ self.client = devcycle_client
27
+ self.meta_data = Metadata(name=f"DevCycle {self.client.get_sdk_platform()}")
28
+
29
+ def get_metadata(self) -> Metadata:
30
+ return self.meta_data
31
+
32
+ def get_provider_hooks(self) -> List[Hook]:
33
+ return []
34
+
35
+ def _resolve(
36
+ self,
37
+ flag_key: str,
38
+ default_value: Any,
39
+ evaluation_context: Optional[EvaluationContext] = None,
40
+ ) -> FlagResolutionDetails[Any]:
41
+ if self.client.is_initialized():
42
+ try:
43
+ user: DevCycleUser = DevCycleUser.create_user_from_context(
44
+ evaluation_context
45
+ )
46
+
47
+ variable = self.client.variable(
48
+ key=flag_key, user=user, default_value=default_value
49
+ )
50
+
51
+ if variable is None:
52
+ # this technically should never happen
53
+ # as the DevCycle client should at least return a default Variable instance
54
+ return FlagResolutionDetails(
55
+ value=default_value,
56
+ reason=Reason.DEFAULT,
57
+ )
58
+ else:
59
+ return FlagResolutionDetails(
60
+ value=variable.value,
61
+ reason=Reason.DEFAULT
62
+ if variable.isDefaulted
63
+ else Reason.TARGETING_MATCH,
64
+ )
65
+ except ValueError as e:
66
+ # occurs if the key or default value is None
67
+ raise InvalidContextError(str(e))
68
+ else:
69
+ return FlagResolutionDetails(
70
+ value=default_value,
71
+ reason=Reason.ERROR,
72
+ error_code=ErrorCode.PROVIDER_NOT_READY,
73
+ )
74
+
75
+ def resolve_boolean_details(
76
+ self,
77
+ flag_key: str,
78
+ default_value: bool,
79
+ evaluation_context: Optional[EvaluationContext] = None,
80
+ ) -> FlagResolutionDetails[bool]:
81
+ return self._resolve(flag_key, default_value, evaluation_context)
82
+
83
+ def resolve_string_details(
84
+ self,
85
+ flag_key: str,
86
+ default_value: str,
87
+ evaluation_context: Optional[EvaluationContext] = None,
88
+ ) -> FlagResolutionDetails[str]:
89
+ return self._resolve(flag_key, default_value, evaluation_context)
90
+
91
+ def resolve_integer_details(
92
+ self,
93
+ flag_key: str,
94
+ default_value: int,
95
+ evaluation_context: Optional[EvaluationContext] = None,
96
+ ) -> FlagResolutionDetails[int]:
97
+ return self._resolve(flag_key, default_value, evaluation_context)
98
+
99
+ def resolve_float_details(
100
+ self,
101
+ flag_key: str,
102
+ default_value: float,
103
+ evaluation_context: Optional[EvaluationContext] = None,
104
+ ) -> FlagResolutionDetails[float]:
105
+ return self._resolve(flag_key, default_value, evaluation_context)
106
+
107
+ def resolve_object_details(
108
+ self,
109
+ flag_key: str,
110
+ default_value: Union[dict, list],
111
+ evaluation_context: Optional[EvaluationContext] = None,
112
+ ) -> FlagResolutionDetails[Union[dict, list]]:
113
+ if not isinstance(default_value, dict):
114
+ raise TypeMismatchError("Default value must be a flat dictionary")
115
+
116
+ if default_value:
117
+ for k, v in default_value.items():
118
+ if not isinstance(v, (str, int, float, bool)) or v is None:
119
+ raise TypeMismatchError(
120
+ "Default value must be a flat dictionary containing only strings, numbers, booleans or None values"
121
+ )
122
+
123
+ return self._resolve(flag_key, default_value, evaluation_context)
@@ -0,0 +1,6 @@
1
+ def slash_join(*args) -> str:
2
+ """
3
+ Assembles a string, concatenating the arguments with a single slash between them
4
+ removing any leading or trailing slashes from the arguments
5
+ """
6
+ return "/".join(str(arg).strip("/") for arg in args)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devcycle-python-server-sdk
3
- Version: 3.3.5
3
+ Version: 3.5.0
4
4
  Summary: DevCycle Python SDK
5
5
  Home-page: https://github.com/devcycleHQ/python-server-sdk
6
6
  Author: UNKNOWN
@@ -13,6 +13,7 @@ Requires-Dist: urllib3 >=1.15.1
13
13
  Requires-Dist: requests ~=2.31.0
14
14
  Requires-Dist: wasmtime ==9.0.0
15
15
  Requires-Dist: protobuf >=4.23.3
16
+ Requires-Dist: openfeature-sdk >=0.7.0
16
17
 
17
18
  The DevCycle Python SDK used for feature management.
18
19
 
@@ -1,56 +1,66 @@
1
- devcycle_python_sdk/VERSION.txt,sha256=ZJgZeDp4ukJLl_z00p-4CYZrCzZZ-QHAwNrCha2cyng,6
2
- devcycle_python_sdk/__init__.py,sha256=vOoglQ8L7RsHTp-GSa8g7k2AAPrR_obhiDSC09LpG8w,265
3
- devcycle_python_sdk/bucketing-lib.release.wasm,sha256=oNVNiF5h5YO4kejqbdogK44DVgWgwn5UJZ7KUsR9aZA,200891
4
- devcycle_python_sdk/cloud_client.py,sha256=wkDNwz8GFfnJybD1e96Lr41SY_2saQUTMD5gByFIbIg,6766
1
+ devcycle_python_sdk/VERSION.txt,sha256=dhIMHyxDEcIVTCKmtY4A5BHg6-dq-uDa9gjZGWxh8RQ,6
2
+ devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
3
+ devcycle_python_sdk/bucketing-lib.release.wasm,sha256=yrNDrAxZS1h9VlTzsdOrNLP4ngnM04PmKxcWop1yrt4,202623
4
+ devcycle_python_sdk/cloud_client.py,sha256=UMfXvXwO4v3pZW9ZA52ivxPH1Lp4Goy5gpV_s0qCpxQ,7352
5
+ devcycle_python_sdk/devcycle_client.py,sha256=W5MeOslkXjTqZtoSy_Bbbug8gVoicjxshpKjDteEr6E,917
5
6
  devcycle_python_sdk/exceptions.py,sha256=Et7fS8pEFbHkFY86T2pJoEUstwjh4_rzhJxVH8fzAns,1053
6
- devcycle_python_sdk/local_client.py,sha256=h5NrP4-x26cz-yla7OUy4SL7VJgplUFH8Fj0jTqz2Os,8845
7
+ devcycle_python_sdk/local_client.py,sha256=5PFEUmBs-QsgaiXWBQfhhjcN2TbB64JH3iVwpJteG7o,9373
7
8
  devcycle_python_sdk/options.py,sha256=Ejr6HbdyqzZajFVkEqWLk5XLfdtqCIkvsyWaRt_lgwU,4575
8
9
  devcycle_python_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
10
  devcycle_python_sdk/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
10
11
  devcycle_python_sdk/api/backoff.py,sha256=F6WYCloaZD9CjNu6mKBMq7K20S8-i2WVU6X05ZRoawE,303
11
- devcycle_python_sdk/api/bucketing_client.py,sha256=cchUuSngJEgDnhR4HeOsCgjH2gJu-yDbz9HFLDT2Bzk,5074
12
- devcycle_python_sdk/api/config_client.py,sha256=jtnDmGQBaxOCxF346iDfqjIMKclqdOZiApXeXLDakbs,4226
13
- devcycle_python_sdk/api/event_client.py,sha256=y1u7HQLTM_FHxiwyZ_2hahnVjYaNv2HuSzlNcIHIiOk,3319
12
+ devcycle_python_sdk/api/bucketing_client.py,sha256=-S0y9uAffippCTfjYgxI5j4j7YNfMxQJYUba1219nps,5111
13
+ devcycle_python_sdk/api/config_client.py,sha256=BbPAjvB8c4engCiVnrRDXAXabHJLKYc1uxCtoPpvffg,4293
14
+ devcycle_python_sdk/api/event_client.py,sha256=WxkuV6geEC9MgG4FN36HeEQQa22ilq2mkOVMZkpiEk4,3356
14
15
  devcycle_python_sdk/api/local_bucketing.py,sha256=-TfG3Jb0syVF3sqzzlFyKxHqLQ6a37C4mW0oj53J-yQ,16332
15
16
  devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  devcycle_python_sdk/managers/config_manager.py,sha256=_-1kWwyyAcwVF_DibXjnehXi3Qj6HR8orYfgNfgWJOc,3248
17
18
  devcycle_python_sdk/managers/event_queue_manager.py,sha256=06TcdlRoB89C7vGH1UTnnoDFsSnT71tPwf0gn1NK-W0,8071
18
19
  devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- devcycle_python_sdk/models/bucketed_config.py,sha256=rSKpxMTF-IbUVQ9gAWrWnb9Q9YF94ATqEFWNWuoQntk,3814
20
+ devcycle_python_sdk/models/bucketed_config.py,sha256=76bdF7pgUdIAn2y8YPs3AO7Qa2eyJQ_jf4QhG3IERaE,3951
20
21
  devcycle_python_sdk/models/error_response.py,sha256=r_dRUDAbral9UGaMYcnBsZOr9Hmkyi1SQbhGmZilgpA,442
21
22
  devcycle_python_sdk/models/event.py,sha256=-6bhKp72mAgaoSxvhS__G4vvfQ9Psj83fc0Igelzw3o,3713
22
23
  devcycle_python_sdk/models/feature.py,sha256=m3wxxIjC5jxhKpPLI4RcsBaNLuy-zsMqoAgc8ySPe0I,852
23
24
  devcycle_python_sdk/models/platform_data.py,sha256=kIFevH5trsxI32qp7BW6glUCpipgpaVBKGqJWzQd5Xo,783
24
- devcycle_python_sdk/models/user.py,sha256=BGMtVM6shDX7rUfGfBbI55IUEsQ_4qrnmtwTqOcB-8A,2616
25
+ devcycle_python_sdk/models/user.py,sha256=57d_n8Yy8g-zsGzVF0hmSf4McPtFPJJ0LLY_ql75FWk,6266
25
26
  devcycle_python_sdk/models/variable.py,sha256=qp00SyCNyfYiMlTwnDSDJ4ZetRF35JHTbYZQOBxFIqY,1711
27
+ devcycle_python_sdk/open_feature_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ devcycle_python_sdk/open_feature_provider/provider.py,sha256=FjNxmyKrnUO4ma4c-Z39qWigLVC_P-L_Nly0mycA1Vc,4475
26
29
  devcycle_python_sdk/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
30
  devcycle_python_sdk/protobuf/utils.py,sha256=zi-kVx6yvgS2c04cUobLd2IDc_ZqKkWduay9YcpjoGc,4684
28
31
  devcycle_python_sdk/protobuf/variableForUserParams_pb2.py,sha256=Zf4-8n67UnulC0Sz90aqLYsXCFEgywOqfiSHuXZomLk,4244
29
32
  devcycle_python_sdk/util/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
33
+ devcycle_python_sdk/util/strings.py,sha256=gH0Xm3gIXNqn6hczKPk10Jn5_9knuD7OB5MrB7bqztw,252
30
34
  devcycle_python_sdk/util/version.py,sha256=Cz7Us_EuXP-T8GI47g2WHYdBJj_zKo-5y545IDHJAjI,253
31
35
  example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
36
  example/cloud_client_example.py,sha256=YD8ZsTv3dPYfbFUKraRpRT0VsJGrPwjm8ZCuA5H54yI,2463
33
- example/local_bucketing_client_example.py,sha256=zSJsllQ5aSvUTj-VFvt0w7pVTMRW6z-gTj9R7ZNPlyE,2797
37
+ example/local_bucketing_client_example.py,sha256=TaIfy3VnW9fXii9NPYs5UOuP4fLKruXMjnZApveqzl4,2796
38
+ example/openfeature_example.py,sha256=kO6F-Xk20l6CPiMfIxSRreshEGmHMFHRg3zbeLXa3AI,2410
34
39
  test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
40
  test/test_cloud_client.py,sha256=zjk302m7qKMCQryolkc1WKLFmbGWUxA0VtHlZk0rY-0,11442
36
- test/test_local_client.py,sha256=mEBhq0gnHzjE-i_w4hMrmfhoHDJvCXGt3G8lx0EA9OE,12671
41
+ test/test_local_client.py,sha256=Ds6VIfLcaefboyvGrhQTGsdJj_DGB4IVnR2_16xDpSY,13022
37
42
  test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- test/api/test_bucketing_client.py,sha256=Zomy0LQ-abI6nZvazSgBZQX6OjPwqBbsmfXR7KpdNOk,7565
39
- test/api/test_config_client.py,sha256=gV86fwGBFN5r81jrfGKyptldgR5YGEXdUdFlXwFaupg,4275
40
- test/api/test_event_client.py,sha256=jaWFWY0jrFI0amCfRsByi-JHPkoWKH_Z7V2nNcbvepM,3211
41
- test/api/test_local_bucketing.py,sha256=E7alurvIScT0nilcuY2xGLCXzwxHrNWPBGdWxG6bR5A,15156
43
+ test/api/test_bucketing_client.py,sha256=M2mjACowLvbsIjXdP-w8-fwY02xqv1X1r_h9v-pXa7A,7739
44
+ test/api/test_config_client.py,sha256=oZMBlXGw4UHb5-rSNF_ARTrAjvbvkbFgb8GAK4ExR14,4413
45
+ test/api/test_event_client.py,sha256=v4Xoum-PdywfczrfMV1llM5vrIJoqHybzxPLVokh09g,3310
46
+ test/api/test_local_bucketing.py,sha256=lPL3ThZcGG-8-67jFxRvGrwL8NB6-sM5qGJb0p2O__Q,15254
42
47
  test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- test/fixture/data.py,sha256=OFHR29FiiMWhh2bmyeEbCck4Jyy8Ah7MsoLg0JZN0vk,1372
48
+ test/fixture/data.py,sha256=6ReLxWPMhCDbJ5Ex24_iR0PJNjKzPFV1I9j_v0FVbBU,1462
44
49
  test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
50
  test/managers/test_config_manager.py,sha256=_MtvyxK06xpBzzEsN1zsLdYrk3DsPsbCmSj7XorINmI,5366
46
51
  test/managers/test_event_queue_manager.py,sha256=KquHBuYiDdoVStm5Ga9mIB-wvmJnChiFRBNC8sMKaxQ,11053
47
52
  test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
53
  test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
54
+ test/models/test_user.py,sha256=ZNTBSKLDrqrsqZuaw5AHtc_FW4empSTaeNPZHZaFlI4,6736
55
+ test/openfeature_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ test/openfeature_test/test_provider.py,sha256=UPxAD5e7NvpIhmqNFCrTY_yrKrMyYxBc2xuMAgdSGvw,7653
57
+ test/openfeature_test/test_provider_local_sdk.py,sha256=5NjfUDalW4F7Oe8n1740VO7l5OIBEOldnSryM98uO2w,4426
49
58
  test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
+ test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
50
60
  test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
51
- test/util/test_version.py,sha256=lx0HFefK_I5D35bmjEc7t5_6oYB4LBWnG-zxqCz1lV4,343
52
- devcycle_python_server_sdk-3.3.5.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
53
- devcycle_python_server_sdk-3.3.5.dist-info/METADATA,sha256=ucHjcHgWX8fpgfMSEursHZMmYSI0twVJlXV5SEMhqnA,471
54
- devcycle_python_server_sdk-3.3.5.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
55
- devcycle_python_server_sdk-3.3.5.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
56
- devcycle_python_server_sdk-3.3.5.dist-info/RECORD,,
61
+ test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
62
+ devcycle_python_server_sdk-3.5.0.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
63
+ devcycle_python_server_sdk-3.5.0.dist-info/METADATA,sha256=IAUJWGuZDWtOoCfJ6Tj6aKURxJODAtUuutO1g94hLho,510
64
+ devcycle_python_server_sdk-3.5.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
65
+ devcycle_python_server_sdk-3.5.0.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
66
+ devcycle_python_server_sdk-3.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.1)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -7,7 +7,7 @@ from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
7
7
  from devcycle_python_sdk.models.user import DevCycleUser
8
8
  from devcycle_python_sdk.models.event import DevCycleEvent, EventType
9
9
 
10
- VARIABLE_KEY = "test-boolean-variable"
10
+ VARIABLE_KEY = "python-example-tests"
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13