launchdarkly-openfeature-server 0.2.2__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.
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/PKG-INFO +5 -6
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/README.md +1 -1
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/provider.py +34 -17
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/pyproject.toml +5 -5
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/test_provider.py +39 -27
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/LICENSE +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/__init__.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/impl/context_converter.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/impl/details_converter.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/ld_openfeature/py.typed +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/__init__.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/impl/test_context_converter.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/impl/test_details_converter.py +0 -0
- {launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/test_data_sources.py +0 -0
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: launchdarkly-openfeature-server
|
|
3
|
-
Version: 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
|
+
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.
|
|
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.
|
|
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
|
|
@@ -22,6 +23,15 @@ class LaunchDarklyProvider(AbstractProvider):
|
|
|
22
23
|
self.__context_converter = EvaluationContextConverter()
|
|
23
24
|
self.__details_converter = ResolutionDetailsConverter()
|
|
24
25
|
|
|
26
|
+
@property
|
|
27
|
+
def client(self) -> LDClient:
|
|
28
|
+
"""
|
|
29
|
+
Access the underlying LaunchDarky client instance backing this provider.
|
|
30
|
+
|
|
31
|
+
This is useful for accessing additional functionality not exposed by the provider.
|
|
32
|
+
"""
|
|
33
|
+
return self.__client
|
|
34
|
+
|
|
25
35
|
def __handle_data_source_status(self, status: DataSourceStatus):
|
|
26
36
|
state = status.state
|
|
27
37
|
if state == DataSourceState.INITIALIZING:
|
|
@@ -141,24 +151,31 @@ class LaunchDarklyProvider(AbstractProvider):
|
|
|
141
151
|
ld_context = self.__context_converter.to_ld_context(evaluation_context)
|
|
142
152
|
result = self.__client.variation_detail(flag_key, ld_context, default_value)
|
|
143
153
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
elif flag_type == FlagType.STRING and not isinstance(result.value, str):
|
|
147
|
-
return self.__mismatched_type_details(default_value)
|
|
148
|
-
elif flag_type == FlagType.INTEGER and isinstance(result.value, bool):
|
|
149
|
-
# Python treats boolean values as instances of int
|
|
150
|
-
return self.__mismatched_type_details(default_value)
|
|
151
|
-
elif flag_type == FlagType.FLOAT and isinstance(result.value, bool):
|
|
152
|
-
# Python treats boolean values as instances of int
|
|
154
|
+
resolved_value = self.__validate_and_cast_value(flag_type, result.value)
|
|
155
|
+
if resolved_value is None:
|
|
153
156
|
return self.__mismatched_type_details(default_value)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return self.__details_converter.to_resolution_details(
|
|
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
|
|
162
179
|
|
|
163
180
|
@staticmethod
|
|
164
181
|
def __mismatched_type_details(default_value: Any) -> FlagResolutionDetails:
|
{launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "launchdarkly-openfeature-server"
|
|
3
|
-
version = "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.
|
|
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.
|
|
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=
|
|
65
|
+
py_version=39
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
[tool.pytest.ini_options]
|
|
@@ -42,6 +42,10 @@ def test_metadata_name_is_correct(provider: LaunchDarklyProvider):
|
|
|
42
42
|
assert provider.get_metadata().name == "launchdarkly-openfeature-server"
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def test_ldclient_is_accessible(provider: LaunchDarklyProvider):
|
|
46
|
+
assert type(provider.client) is LDClient
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
def test_not_providing_context_returns_error(provider: LaunchDarklyProvider):
|
|
46
50
|
resolution_details = provider.resolve_boolean_details("flag-key", True, None)
|
|
47
51
|
|
|
@@ -84,32 +88,38 @@ def test_invalid_types_generate_type_mismatch_results(provider: LaunchDarklyProv
|
|
|
84
88
|
|
|
85
89
|
|
|
86
90
|
@pytest.mark.parametrize(
|
|
87
|
-
"default_value,return_value,expected_value,method_name",
|
|
88
|
-
|
|
89
|
-
pytest.param(True, False, False, 'resolve_boolean_details'),
|
|
90
|
-
pytest.param(False, True, True, 'resolve_boolean_details'),
|
|
91
|
-
pytest.param(False, 1, False, 'resolve_boolean_details'),
|
|
92
|
-
pytest.param(False, "True", False, 'resolve_boolean_details'),
|
|
93
|
-
pytest.param(True, [], True, 'resolve_boolean_details'),
|
|
94
|
-
|
|
95
|
-
pytest.param('default-string', 'return-string', 'return-string', 'resolve_string_details'),
|
|
96
|
-
pytest.param('default-string', 1, 'default-string', 'resolve_string_details'),
|
|
97
|
-
pytest.param('default-string', True, 'default-string', 'resolve_string_details'),
|
|
98
|
-
|
|
99
|
-
pytest.param(1, 2, 2, 'resolve_integer_details'),
|
|
100
|
-
pytest.param(1, True, 1, 'resolve_integer_details'),
|
|
101
|
-
pytest.param(1, False, 1, 'resolve_integer_details'),
|
|
102
|
-
pytest.param(1, "", 1, 'resolve_integer_details'),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
pytest.param(1.0, 2, 2.0, 'resolve_float_details'),
|
|
106
|
-
pytest.param(1.0,
|
|
107
|
-
pytest.param(1.0,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pytest.param(['default-value'],
|
|
111
|
-
pytest.param(['default-value'],
|
|
112
|
-
pytest.param(['default-value'],
|
|
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'),
|
|
113
123
|
],
|
|
114
124
|
)
|
|
115
125
|
def test_check_method_and_result_match_type(
|
|
@@ -117,6 +127,7 @@ def test_check_method_and_result_match_type(
|
|
|
117
127
|
default_value: Union[bool, str, int, float, List],
|
|
118
128
|
return_value: Union[bool, str, int, float, List],
|
|
119
129
|
expected_value: Union[bool, str, int, float, List],
|
|
130
|
+
expected_type: type,
|
|
120
131
|
method_name: str,
|
|
121
132
|
# end of parameterized values
|
|
122
133
|
test_data_source: TestData,
|
|
@@ -126,8 +137,9 @@ def test_check_method_and_result_match_type(
|
|
|
126
137
|
|
|
127
138
|
method = getattr(provider, method_name)
|
|
128
139
|
resolution_details = method("check-method-flag", default_value, evaluation_context)
|
|
129
|
-
|
|
140
|
+
|
|
130
141
|
assert resolution_details.value == expected_value
|
|
142
|
+
#assert isinstance(resolution_details.value, expected_type)
|
|
131
143
|
|
|
132
144
|
|
|
133
145
|
def test_logger_changes_should_cascade_to_evaluation_converter(provider: LaunchDarklyProvider, caplog):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{launchdarkly_openfeature_server-0.2.2 → launchdarkly_openfeature_server-0.4.2}/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|