launchdarkly-openfeature-server 0.8.0__tar.gz → 0.8.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.8.0 → launchdarkly_openfeature_server-0.8.2}/PKG-INFO +5 -2
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/README.md +3 -1
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/ld_openfeature/impl/context_converter.py +30 -2
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/ld_openfeature/provider.py +20 -1
- launchdarkly_openfeature_server-0.8.2/ld_openfeature/version.py +1 -0
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/pyproject.toml +1 -1
- launchdarkly_openfeature_server-0.8.0/ld_openfeature/version.py +0 -1
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/LICENSE +0 -0
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/ld_openfeature/__init__.py +0 -0
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/ld_openfeature/impl/details_converter.py +0 -0
- {launchdarkly_openfeature_server-0.8.0 → launchdarkly_openfeature_server-0.8.2}/ld_openfeature/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: launchdarkly-openfeature-server
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: An OpenFeature provider for the LaunchDarkly Python server SDK
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
20
|
Classifier: Topic :: Software Development
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
22
|
Requires-Dist: launchdarkly-server-sdk (>=9.17.0,<10)
|
|
@@ -63,7 +64,7 @@ This matrix mirrors the [feature matrix of the OpenFeature SDK for Python](https
|
|
|
63
64
|
| ✅ | Hooks | Hooks are registered on the OpenFeature API and client; the provider requires no additional support and its results are visible to hooks, including [flag metadata](#flag-metadata). |
|
|
64
65
|
| ✅ | Logging | The provider logs through the logging configuration of the `Config` it is given. |
|
|
65
66
|
| ✅ | Domains | Domains bind clients to providers in the OpenFeature SDK; a separate provider instance may be registered per domain. |
|
|
66
|
-
| ✅ | Eventing | LaunchDarkly data source status changes are emitted as `PROVIDER_READY`, `PROVIDER_STALE` and `PROVIDER_ERROR
|
|
67
|
+
| ✅ | Eventing | LaunchDarkly data source status changes are emitted as `PROVIDER_READY`, `PROVIDER_STALE` and `PROVIDER_ERROR`, and only when the status changes; flag changes as `PROVIDER_CONFIGURATION_CHANGED` with the changed flag key. |
|
|
67
68
|
| ✅ | Tracking | `track` sends a LaunchDarkly custom event for the evaluation context, with the tracking event value and remaining details attached. |
|
|
68
69
|
| ✅ | Initialization | `initialize` reports whether the LaunchDarkly client became ready. The optional `start_wait` parameter bounds initialization; zero applies no timeout and waits until the data source becomes valid or permanently fails. |
|
|
69
70
|
| ✅ | Shutdown | `shutdown` closes the LaunchDarkly client; a closed client cannot be restarted, so a new provider instance is required afterward. |
|
|
@@ -121,6 +122,8 @@ There are several other attributes which have special functionality within a sin
|
|
|
121
122
|
- A key of `anonymous`. Must be a boolean value. [Equivalent to the 'anonymous' builder method in the SDK.](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.ContextBuilder.anonymous)
|
|
122
123
|
- A key of `name`. Must be a string. [Equivalent to the 'name' builder method in the SDK.](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.ContextBuilder.name)
|
|
123
124
|
|
|
125
|
+
The LaunchDarkly context model has no date type, so a `datetime` attribute is converted to a string containing its ISO-8601 representation in UTC, which is the format the LaunchDarkly date targeting operators accept. A `datetime` without a timezone is treated as UTC. This conversion is applied to `datetime` values nested in lists and dictionaries as well.
|
|
126
|
+
|
|
124
127
|
### Examples
|
|
125
128
|
|
|
126
129
|
#### A single user context
|
|
@@ -37,7 +37,7 @@ This matrix mirrors the [feature matrix of the OpenFeature SDK for Python](https
|
|
|
37
37
|
| ✅ | Hooks | Hooks are registered on the OpenFeature API and client; the provider requires no additional support and its results are visible to hooks, including [flag metadata](#flag-metadata). |
|
|
38
38
|
| ✅ | Logging | The provider logs through the logging configuration of the `Config` it is given. |
|
|
39
39
|
| ✅ | Domains | Domains bind clients to providers in the OpenFeature SDK; a separate provider instance may be registered per domain. |
|
|
40
|
-
| ✅ | Eventing | LaunchDarkly data source status changes are emitted as `PROVIDER_READY`, `PROVIDER_STALE` and `PROVIDER_ERROR
|
|
40
|
+
| ✅ | Eventing | LaunchDarkly data source status changes are emitted as `PROVIDER_READY`, `PROVIDER_STALE` and `PROVIDER_ERROR`, and only when the status changes; flag changes as `PROVIDER_CONFIGURATION_CHANGED` with the changed flag key. |
|
|
41
41
|
| ✅ | Tracking | `track` sends a LaunchDarkly custom event for the evaluation context, with the tracking event value and remaining details attached. |
|
|
42
42
|
| ✅ | Initialization | `initialize` reports whether the LaunchDarkly client became ready. The optional `start_wait` parameter bounds initialization; zero applies no timeout and waits until the data source becomes valid or permanently fails. |
|
|
43
43
|
| ✅ | Shutdown | `shutdown` closes the LaunchDarkly client; a closed client cannot be restarted, so a new provider instance is required afterward. |
|
|
@@ -95,6 +95,8 @@ There are several other attributes which have special functionality within a sin
|
|
|
95
95
|
- A key of `anonymous`. Must be a boolean value. [Equivalent to the 'anonymous' builder method in the SDK.](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.ContextBuilder.anonymous)
|
|
96
96
|
- A key of `name`. Must be a string. [Equivalent to the 'name' builder method in the SDK.](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#ldclient.ContextBuilder.name)
|
|
97
97
|
|
|
98
|
+
The LaunchDarkly context model has no date type, so a `datetime` attribute is converted to a string containing its ISO-8601 representation in UTC, which is the format the LaunchDarkly date targeting operators accept. A `datetime` without a timezone is treated as UTC. This conversion is applied to `datetime` values nested in lists and dictionaries as well.
|
|
99
|
+
|
|
98
100
|
### Examples
|
|
99
101
|
|
|
100
102
|
#### A single user context
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
1
2
|
from logging import getLogger
|
|
2
|
-
from typing import Any, Dict, List, Mapping, Optional
|
|
3
|
+
from typing import Any, Dict, List, Mapping, Optional, Sequence
|
|
3
4
|
|
|
4
5
|
from ldclient.context import Context, ContextBuilder, ContextMultiBuilder
|
|
5
6
|
from openfeature.evaluation_context import EvaluationContext, EvaluationContextAttribute
|
|
@@ -8,6 +9,33 @@ from openfeature.evaluation_context import EvaluationContext, EvaluationContextA
|
|
|
8
9
|
logger = getLogger("launchdarkly-openfeature-server")
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def _to_ld_value(value: Any) -> Any:
|
|
13
|
+
"""
|
|
14
|
+
Convert an OpenFeature attribute value into a value the LaunchDarkly SDK can represent.
|
|
15
|
+
|
|
16
|
+
A datetime becomes its ISO-8601 representation in UTC, because the LaunchDarkly value model has
|
|
17
|
+
no date type. A datetime without a timezone is treated as UTC. A value which cannot be
|
|
18
|
+
converted becomes None.
|
|
19
|
+
"""
|
|
20
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
if isinstance(value, datetime):
|
|
24
|
+
if value.tzinfo is None:
|
|
25
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
26
|
+
|
|
27
|
+
return value.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z')
|
|
28
|
+
|
|
29
|
+
if isinstance(value, Mapping):
|
|
30
|
+
return {str(k): _to_ld_value(v) for k, v in value.items()}
|
|
31
|
+
|
|
32
|
+
if isinstance(value, Sequence):
|
|
33
|
+
return [_to_ld_value(item) for item in value]
|
|
34
|
+
|
|
35
|
+
logger.error("The attribute value of type %s could not be converted; using None instead", type(value).__name__)
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
11
39
|
class EvaluationContextConverter:
|
|
12
40
|
def to_ld_context(self, context: EvaluationContext) -> Context:
|
|
13
41
|
"""
|
|
@@ -108,6 +136,6 @@ class EvaluationContextConverter:
|
|
|
108
136
|
elif k == 'privateAttributes':
|
|
109
137
|
logger.error("The attribute 'privateAttributes' must be an array")
|
|
110
138
|
else:
|
|
111
|
-
builder.set(k, v)
|
|
139
|
+
builder.set(k, _to_ld_value(v))
|
|
112
140
|
|
|
113
141
|
return builder.build()
|
|
@@ -44,6 +44,9 @@ class LaunchDarklyProvider(AbstractProvider):
|
|
|
44
44
|
self.__context_converter = EvaluationContextConverter()
|
|
45
45
|
self.__details_converter = ResolutionDetailsConverter()
|
|
46
46
|
|
|
47
|
+
self.__status_lock = threading.Lock()
|
|
48
|
+
self.__last_emitted_state: Optional[DataSourceState] = None
|
|
49
|
+
|
|
47
50
|
@property
|
|
48
51
|
def client(self) -> LDClient:
|
|
49
52
|
"""
|
|
@@ -53,11 +56,27 @@ class LaunchDarklyProvider(AbstractProvider):
|
|
|
53
56
|
"""
|
|
54
57
|
return self.__client
|
|
55
58
|
|
|
59
|
+
def __is_new_status(self, state: DataSourceState) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Report whether a state changes the provider status. Several data source states can map to the same provider
|
|
62
|
+
status, and a repeated status is not a change an application can act on.
|
|
63
|
+
"""
|
|
64
|
+
with self.__status_lock:
|
|
65
|
+
if state == self.__last_emitted_state:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
self.__last_emitted_state = state
|
|
69
|
+
return True
|
|
70
|
+
|
|
56
71
|
def __handle_data_source_status(self, status: DataSourceStatus):
|
|
57
72
|
state = status.state
|
|
58
73
|
if state == DataSourceState.INITIALIZING:
|
|
59
74
|
return
|
|
60
|
-
|
|
75
|
+
|
|
76
|
+
if not self.__is_new_status(state):
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
if state == DataSourceState.VALID:
|
|
61
80
|
self.emit_provider_ready(ProviderEventDetails())
|
|
62
81
|
elif state == DataSourceState.OFF:
|
|
63
82
|
error_message = self.__get_message(status,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.8.2" # x-release-please-version
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.8.0" # x-release-please-version
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|