1
+ import threading
1
2
from typing import Any , List , Optional , Union
2
3
3
4
from ldclient import LDClient , Config
5
+ from ldclient .interfaces import DataSourceStatus , FlagChange , DataSourceState
4
6
from openfeature .evaluation_context import EvaluationContext
5
- from openfeature .exception import ErrorCode
7
+ from openfeature .exception import ErrorCode , ProviderFatalError
6
8
from openfeature .flag_evaluation import FlagResolutionDetails , FlagType , Reason
7
9
from openfeature .hook import Hook
8
10
from openfeature .provider .metadata import Metadata
9
- from openfeature .provider .provider import AbstractProvider
11
+ from openfeature .provider import AbstractProvider
12
+ from openfeature .event import ProviderEventDetails
10
13
11
14
from ld_openfeature .impl .context_converter import EvaluationContextConverter
12
15
from ld_openfeature .impl .details_converter import ResolutionDetailsConverter
@@ -19,7 +22,60 @@ def __init__(self, config: Config):
19
22
self .__context_converter = EvaluationContextConverter ()
20
23
self .__details_converter = ResolutionDetailsConverter ()
21
24
25
+ def __handle_data_source_status (self , status : DataSourceStatus ):
26
+ state = status .state
27
+ if state == DataSourceState .INITIALIZING :
28
+ return
29
+ elif state == DataSourceState .VALID :
30
+ self .emit_provider_ready (ProviderEventDetails ())
31
+ elif state == DataSourceState .OFF :
32
+ error_message = self .__get_message (status ,
33
+ "the provider has encountered a permanent error or has been shutdown" )
34
+ self .emit_provider_error (ProviderEventDetails (error_code = ErrorCode .PROVIDER_FATAL ,
35
+ message = error_message ))
36
+ elif state == DataSourceState .INTERRUPTED :
37
+ error_message = self .__get_message (status , "encountered an unknown error" )
38
+ self .emit_provider_stale (ProviderEventDetails (message = error_message ))
39
+
40
+ # For now treat an unknown state as no change.
41
+
42
+ def __handle_flag_change (self , change : FlagChange ):
43
+ self .emit_provider_configuration_changed (ProviderEventDetails (flags_changed = [change .key ]))
44
+ pass
45
+
46
+ def initialize (self , evaluation_context : EvaluationContext ):
47
+ ready_event = threading .Event ()
48
+
49
+ def ready_handler (status : DataSourceStatus ):
50
+ if status .state == DataSourceState .VALID :
51
+ ready_event .set ()
52
+ elif status .state == DataSourceState .OFF :
53
+ ready_event .set ()
54
+
55
+ # We listen just to handle the ready event. We do not emit events because the client emits them for us.
56
+ self .__client .data_source_status_provider .add_listener (ready_handler )
57
+
58
+ # Check for conditions that may have happened before we added the listener.
59
+ if self .__client .data_source_status_provider .status .state == DataSourceState .OFF :
60
+ ready_event .set ()
61
+
62
+ if self .__client .is_initialized ():
63
+ ready_event .set ()
64
+
65
+ ready_event .wait ()
66
+
67
+ self .__client .data_source_status_provider .remove_listener (ready_handler )
68
+
69
+ if not self .__client .is_initialized ():
70
+ raise ProviderFatalError (error_message = "launchdarkly client initialization failed" )
71
+
72
+ # Listen to new status events and emit them.
73
+ self .__client .data_source_status_provider .add_listener (self .__handle_data_source_status )
74
+ self .__client .flag_tracker .add_listener (self .__handle_flag_change )
75
+
22
76
def shutdown (self ):
77
+ self .__client .data_source_status_provider .remove_listener (self .__handle_data_source_status )
78
+ self .__client .flag_tracker .remove_listener (self .__handle_flag_change )
23
79
self .__client .close ()
24
80
25
81
def get_metadata (self ) -> Metadata :
@@ -73,7 +129,8 @@ def resolve_object_details(
73
129
"""Resolves the flag value for the provided flag key as a list or dictionary"""
74
130
return self .__resolve_value (FlagType (FlagType .OBJECT ), flag_key , default_value , evaluation_context )
75
131
76
- def __resolve_value (self , flag_type : FlagType , flag_key : str , default_value : Any , evaluation_context : Optional [EvaluationContext ] = None ) -> FlagResolutionDetails :
132
+ def __resolve_value (self , flag_type : FlagType , flag_key : str , default_value : Any ,
133
+ evaluation_context : Optional [EvaluationContext ] = None ) -> FlagResolutionDetails :
77
134
if evaluation_context is None :
78
135
return FlagResolutionDetails (
79
136
value = default_value ,
@@ -103,9 +160,16 @@ def __resolve_value(self, flag_type: FlagType, flag_key: str, default_value: Any
103
160
104
161
return self .__details_converter .to_resolution_details (result )
105
162
106
- def __mismatched_type_details (self , default_value : Any ) -> FlagResolutionDetails :
163
+ @staticmethod
164
+ def __mismatched_type_details (default_value : Any ) -> FlagResolutionDetails :
107
165
return FlagResolutionDetails (
108
166
value = default_value ,
109
167
reason = Reason (Reason .ERROR ),
110
168
error_code = ErrorCode .TYPE_MISMATCH
111
169
)
170
+
171
+ @staticmethod
172
+ def __get_message (status : DataSourceStatus , default : str ):
173
+ if status .error and status .error .message :
174
+ return status .error .message
175
+ return default
0 commit comments