1+ import threading
12from typing import Any , List , Optional , Union
23
34from ldclient import LDClient , Config
5+ from ldclient .interfaces import DataSourceStatus , FlagChange , DataSourceState
46from openfeature .evaluation_context import EvaluationContext
5- from openfeature .exception import ErrorCode
7+ from openfeature .exception import ErrorCode , ProviderFatalError
68from openfeature .flag_evaluation import FlagResolutionDetails , FlagType , Reason
79from openfeature .hook import Hook
810from openfeature .provider .metadata import Metadata
9- from openfeature .provider .provider import AbstractProvider
11+ from openfeature .provider import AbstractProvider
12+ from openfeature .event import ProviderEventDetails
1013
1114from ld_openfeature .impl .context_converter import EvaluationContextConverter
1215from ld_openfeature .impl .details_converter import ResolutionDetailsConverter
@@ -19,7 +22,60 @@ def __init__(self, config: Config):
1922 self .__context_converter = EvaluationContextConverter ()
2023 self .__details_converter = ResolutionDetailsConverter ()
2124
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+
2276 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 )
2379 self .__client .close ()
2480
2581 def get_metadata (self ) -> Metadata :
@@ -73,7 +129,8 @@ def resolve_object_details(
73129 """Resolves the flag value for the provided flag key as a list or dictionary"""
74130 return self .__resolve_value (FlagType (FlagType .OBJECT ), flag_key , default_value , evaluation_context )
75131
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 :
77134 if evaluation_context is None :
78135 return FlagResolutionDetails (
79136 value = default_value ,
@@ -103,9 +160,16 @@ def __resolve_value(self, flag_type: FlagType, flag_key: str, default_value: Any
103160
104161 return self .__details_converter .to_resolution_details (result )
105162
106- def __mismatched_type_details (self , default_value : Any ) -> FlagResolutionDetails :
163+ @staticmethod
164+ def __mismatched_type_details (default_value : Any ) -> FlagResolutionDetails :
107165 return FlagResolutionDetails (
108166 value = default_value ,
109167 reason = Reason (Reason .ERROR ),
110168 error_code = ErrorCode .TYPE_MISMATCH
111169 )
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