launchdarkly-openfeature-server 0.3.0__tar.gz → 0.4.2__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 (14) hide show
  1. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/PKG-INFO +5 -6
  2. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/README.md +1 -1
  3. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/provider.py +25 -17
  4. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/pyproject.toml +5 -5
  5. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/tests/test_provider.py +35 -27
  6. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/LICENSE +0 -0
  7. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/__init__.py +0 -0
  8. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/impl/context_converter.py +0 -0
  9. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/impl/details_converter.py +0 -0
  10. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/py.typed +0 -0
  11. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/tests/__init__.py +0 -0
  12. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/tests/impl/test_context_converter.py +0 -0
  13. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/tests/impl/test_details_converter.py +0 -0
  14. {launchdarkly_openfeature_server-0.3.0 → launchdarkly_openfeature_server-0.4.2}/tests/test_data_sources.py +0 -0
@@ -1,21 +1,20 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: launchdarkly-openfeature-server
3
- Version: 0.3.0
3
+ Version: 0.4.2
4
4
  Summary: An OpenFeature provider for the LaunchDarkly Python server SDK
5
- Home-page: https://github.com/launchdarkly/openfeature-python-server
6
5
  License: Apache-2.0
7
6
  Author: LaunchDarkly
8
7
  Author-email: dev@launchdarkly.com
9
- Requires-Python: >=3.8,<4.0
8
+ Requires-Python: >=3.9,<4.0
10
9
  Classifier: Intended Audience :: Developers
11
10
  Classifier: License :: OSI Approved :: Apache Software License
12
11
  Classifier: Operating System :: OS Independent
13
12
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
13
  Classifier: Programming Language :: Python :: 3.9
16
14
  Classifier: Programming Language :: Python :: 3.10
17
15
  Classifier: Programming Language :: Python :: 3.11
18
16
  Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Topic :: Software Development
20
19
  Classifier: Topic :: Software Development :: Libraries
21
20
  Requires-Dist: launchdarkly-server-sdk (<10)
@@ -50,7 +49,7 @@ This provider is designed primarily for use in multi-user systems such as web se
50
49
 
51
50
  ## Supported Python versions
52
51
 
53
- This version of the LaunchDarkly provider works with Python 3.8 and above.
52
+ This version of the LaunchDarkly provider works with Python 3.9 and above.
54
53
 
55
54
  ## Getting started
56
55
 
@@ -24,7 +24,7 @@ This provider is designed primarily for use in multi-user systems such as web se
24
24
 
25
25
  ## Supported Python versions
26
26
 
27
- This version of the LaunchDarkly provider works with Python 3.8 and above.
27
+ This version of the LaunchDarkly provider works with Python 3.9 and above.
28
28
 
29
29
  ## Getting started
30
30
 
@@ -1,6 +1,7 @@
1
1
  import threading
2
2
  from typing import Any, List, Optional, Union
3
3
 
4
+ from ldclient.evaluation import EvaluationDetail
4
5
  from ldclient import LDClient, Config
5
6
  from ldclient.interfaces import DataSourceStatus, FlagChange, DataSourceState
6
7
  from openfeature.evaluation_context import EvaluationContext
@@ -150,24 +151,31 @@ class LaunchDarklyProvider(AbstractProvider):
150
151
  ld_context = self.__context_converter.to_ld_context(evaluation_context)
151
152
  result = self.__client.variation_detail(flag_key, ld_context, default_value)
152
153
 
153
- if flag_type == FlagType.BOOLEAN and not isinstance(result.value, bool):
154
+ resolved_value = self.__validate_and_cast_value(flag_type, result.value)
155
+ if resolved_value is None:
154
156
  return self.__mismatched_type_details(default_value)
155
- elif flag_type == FlagType.STRING and not isinstance(result.value, str):
156
- return self.__mismatched_type_details(default_value)
157
- elif flag_type == FlagType.INTEGER and isinstance(result.value, bool):
158
- # Python treats boolean values as instances of int
159
- return self.__mismatched_type_details(default_value)
160
- elif flag_type == FlagType.FLOAT and isinstance(result.value, bool):
161
- # Python treats boolean values as instances of int
162
- return self.__mismatched_type_details(default_value)
163
- elif flag_type == FlagType.INTEGER and not isinstance(result.value, int):
164
- return self.__mismatched_type_details(default_value)
165
- elif flag_type == FlagType.FLOAT and not isinstance(result.value, float) and not isinstance(result.value, int):
166
- return self.__mismatched_type_details(default_value)
167
- elif flag_type == FlagType.OBJECT and not isinstance(result.value, dict) and not isinstance(result.value, list):
168
- return self.__mismatched_type_details(default_value)
169
-
170
- return self.__details_converter.to_resolution_details(result)
157
+
158
+ resolved_detail = EvaluationDetail(
159
+ value=resolved_value,
160
+ variation_index=result.variation_index,
161
+ reason=result.reason,
162
+ )
163
+
164
+ return self.__details_converter.to_resolution_details(resolved_detail)
165
+
166
+ def __validate_and_cast_value(self, flag_type: FlagType, value: Any):
167
+ """Serializes the raw flag value to the expected type based on flag_type."""
168
+ if flag_type == FlagType.BOOLEAN and isinstance(value, bool):
169
+ return value
170
+ elif flag_type == FlagType.STRING and isinstance(value, str):
171
+ return value
172
+ elif flag_type == FlagType.INTEGER and isinstance(value, (int, float)) and not isinstance(value, bool):
173
+ return int(value) # Float decimals are truncated to int
174
+ elif flag_type == FlagType.FLOAT and isinstance(value, (int, float)) and not isinstance(value, bool):
175
+ return float(value)
176
+ elif flag_type == FlagType.OBJECT and isinstance(value, (dict, list)):
177
+ return value
178
+ return None
171
179
 
172
180
  @staticmethod
173
181
  def __mismatched_type_details(default_value: Any) -> FlagResolutionDetails:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "launchdarkly-openfeature-server"
3
- version = "0.3.0"
3
+ version = "0.4.2"
4
4
  description = "An OpenFeature provider for the LaunchDarkly Python server SDK"
5
5
  authors = ["LaunchDarkly <dev@launchdarkly.com>"]
6
6
  license = "Apache-2.0"
@@ -12,11 +12,11 @@ classifiers = [
12
12
  "License :: OSI Approved :: Apache Software License",
13
13
  "Operating System :: OS Independent",
14
14
  "Programming Language :: Python :: 3",
15
- "Programming Language :: Python :: 3.8",
16
15
  "Programming Language :: Python :: 3.9",
17
16
  "Programming Language :: Python :: 3.10",
18
17
  "Programming Language :: Python :: 3.11",
19
18
  "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
20
  "Topic :: Software Development",
21
21
  "Topic :: Software Development :: Libraries",
22
22
  ]
@@ -27,7 +27,7 @@ packages = [
27
27
 
28
28
 
29
29
  [tool.poetry.dependencies]
30
- python = "^3.8"
30
+ python = "^3.9"
31
31
  openfeature-sdk = ">=0.7.0,<1"
32
32
  launchdarkly-server-sdk = "<10"
33
33
 
@@ -56,13 +56,13 @@ jinja2 = "3.1.3"
56
56
 
57
57
 
58
58
  [tool.mypy]
59
- python_version = "3.8"
59
+ python_version = "3.9"
60
60
  install_types = true
61
61
  non_interactive = true
62
62
 
63
63
 
64
64
  [tool.isort]
65
- py_version=38
65
+ py_version=39
66
66
 
67
67
 
68
68
  [tool.pytest.ini_options]
@@ -88,32 +88,38 @@ def test_invalid_types_generate_type_mismatch_results(provider: LaunchDarklyProv
88
88
 
89
89
 
90
90
  @pytest.mark.parametrize(
91
- "default_value,return_value,expected_value,method_name",
92
- [
93
- pytest.param(True, False, False, 'resolve_boolean_details'),
94
- pytest.param(False, True, True, 'resolve_boolean_details'),
95
- pytest.param(False, 1, False, 'resolve_boolean_details'),
96
- pytest.param(False, "True", False, 'resolve_boolean_details'),
97
- pytest.param(True, [], True, 'resolve_boolean_details'),
98
-
99
- pytest.param('default-string', 'return-string', 'return-string', 'resolve_string_details'),
100
- pytest.param('default-string', 1, 'default-string', 'resolve_string_details'),
101
- pytest.param('default-string', True, 'default-string', 'resolve_string_details'),
102
-
103
- pytest.param(1, 2, 2, 'resolve_integer_details'),
104
- pytest.param(1, True, 1, 'resolve_integer_details'),
105
- pytest.param(1, False, 1, 'resolve_integer_details'),
106
- pytest.param(1, "", 1, 'resolve_integer_details'),
107
-
108
- pytest.param(1.0, 2.0, 2.0, 'resolve_float_details'),
109
- pytest.param(1.0, 2, 2.0, 'resolve_float_details'),
110
- pytest.param(1.0, True, 1.0, 'resolve_float_details'),
111
- pytest.param(1.0, 'return-string', 1.0, 'resolve_float_details'),
112
-
113
- pytest.param(['default-value'], ['return-string'], ['return-string'], 'resolve_object_details'),
114
- pytest.param(['default-value'], True, ['default-value'], 'resolve_object_details'),
115
- pytest.param(['default-value'], 1, ['default-value'], 'resolve_object_details'),
116
- pytest.param(['default-value'], 'return-string', ['default-value'], 'resolve_object_details'),
91
+ "default_value,return_value,expected_value,expected_type,method_name",
92
+ [
93
+ pytest.param(True, False, False, bool, 'resolve_boolean_details'),
94
+ pytest.param(False, True, True, bool, 'resolve_boolean_details'),
95
+ pytest.param(False, 1, False, bool, 'resolve_boolean_details'),
96
+ pytest.param(False, "True", False, bool, 'resolve_boolean_details'),
97
+ pytest.param(True, [], True, bool, 'resolve_boolean_details'),
98
+
99
+ pytest.param('default-string', 'return-string', 'return-string', str, 'resolve_string_details'),
100
+ pytest.param('default-string', 1, 'default-string', str, 'resolve_string_details'),
101
+ pytest.param('default-string', True, 'default-string', str, 'resolve_string_details'),
102
+
103
+ pytest.param(1, 2, 2, int, 'resolve_integer_details'),
104
+ pytest.param(1, True, 1, int, 'resolve_integer_details'),
105
+ pytest.param(1, False, 1, int, 'resolve_integer_details'),
106
+ pytest.param(1, "", 1, int, 'resolve_integer_details'),
107
+ pytest.param(1, 2.9, 2, int, 'resolve_integer_details'),
108
+
109
+ pytest.param(1.0, 2.0, 2.0, float, 'resolve_float_details'),
110
+ pytest.param(1.0, 2, 2.0, float, 'resolve_float_details'),
111
+ pytest.param(1.0, True, 1.0, float, 'resolve_float_details'),
112
+ pytest.param(1.0, 'return-string', 1.0, float, 'resolve_float_details'),
113
+
114
+ pytest.param(['default-value'], ['return-string'], ['return-string'], list, 'resolve_object_details'),
115
+ pytest.param(['default-value'], True, ['default-value'], list, 'resolve_object_details'),
116
+ pytest.param(['default-value'], 1, ['default-value'], list, 'resolve_object_details'),
117
+ pytest.param(['default-value'], 'return-string', ['default-value'], list, 'resolve_object_details'),
118
+
119
+ pytest.param({'key': 'default'}, {'key': 'return'}, {'key': 'return'}, dict, 'resolve_object_details'),
120
+ pytest.param({'key': 'default'}, True, {'key': 'default'}, dict, 'resolve_object_details'),
121
+ pytest.param({'key': 'default'}, 1, {'key': 'default'}, dict, 'resolve_object_details'),
122
+ pytest.param({'key': 'default'}, 'return-string', {'key': 'default'}, dict, 'resolve_object_details'),
117
123
  ],
118
124
  )
119
125
  def test_check_method_and_result_match_type(
@@ -121,6 +127,7 @@ def test_check_method_and_result_match_type(
121
127
  default_value: Union[bool, str, int, float, List],
122
128
  return_value: Union[bool, str, int, float, List],
123
129
  expected_value: Union[bool, str, int, float, List],
130
+ expected_type: type,
124
131
  method_name: str,
125
132
  # end of parameterized values
126
133
  test_data_source: TestData,
@@ -130,8 +137,9 @@ def test_check_method_and_result_match_type(
130
137
 
131
138
  method = getattr(provider, method_name)
132
139
  resolution_details = method("check-method-flag", default_value, evaluation_context)
133
-
140
+
134
141
  assert resolution_details.value == expected_value
142
+ #assert isinstance(resolution_details.value, expected_type)
135
143
 
136
144
 
137
145
  def test_logger_changes_should_cascade_to_evaluation_converter(provider: LaunchDarklyProvider, caplog):