1+ import json
12import typing
23
3- from openfeature .contrib .provider .flagd .resolvers .process .connector .file_watcher import (
4- FileWatcher ,
5- )
4+ from openfeature .contrib .tools .flagd .core import FlagdCore
65from openfeature .evaluation_context import EvaluationContext
76from openfeature .event import ProviderEventDetails
8- from openfeature .exception import FlagNotFoundError , GeneralError , ParseError
9- from openfeature .flag_evaluation import FlagResolutionDetails , FlagValueType , Reason
7+ from openfeature .flag_evaluation import FlagResolutionDetails , FlagValueType
108
119from ..config import Config
1210from .process .connector import FlagStateConnector
11+ from .process .connector .file_watcher import FileWatcher
1312from .process .connector .grpc_watcher import GrpcWatcher
14- from .process .flags import Flag , FlagStore
15- from .process .targeting import targeting
1613
1714T = typing .TypeVar ("T" )
1815
1916
20- def _merge_metadata (
21- flag_metadata : typing .Mapping [str , float | int | str | bool ] | None ,
22- flag_set_metadata : typing .Mapping [str , float | int | str | bool ] | None ,
23- ) -> typing .Mapping [str , float | int | str | bool ]:
24- metadata = {} if flag_set_metadata is None else dict (flag_set_metadata )
17+ class _FlagStoreAdapter :
18+ """Bridges FlagdCore with connectors that expect a FlagStore-like update() interface."""
2519
26- if flag_metadata is not None :
27- for key , value in flag_metadata .items ():
28- metadata [key ] = value
29-
30- return metadata
20+ def __init__ (
21+ self ,
22+ evaluator : FlagdCore ,
23+ emit_provider_configuration_changed : typing .Callable [
24+ [ProviderEventDetails ], None
25+ ],
26+ ):
27+ self .evaluator = evaluator
28+ self .emit_provider_configuration_changed = emit_provider_configuration_changed
29+
30+ def update (self , flags_data : dict ) -> None :
31+ json_str = json .dumps (flags_data )
32+ changed_keys = self .evaluator .set_flags_and_get_changed_keys (json_str )
33+ metadata = self .evaluator .get_flag_set_metadata ()
34+ self .emit_provider_configuration_changed (
35+ ProviderEventDetails (flags_changed = changed_keys , metadata = dict (metadata ))
36+ )
3137
3238
3339class InProcessResolver :
@@ -42,15 +48,25 @@ def __init__(
4248 ],
4349 ):
4450 self .config = config
45- self .flag_store = FlagStore (emit_provider_configuration_changed )
51+ self .evaluator = FlagdCore ()
52+
53+ # Adapter lets connectors push flag data to FlagdCore via the
54+ # same .update(dict) interface they used with the old FlagStore.
55+ flag_store_adapter = _FlagStoreAdapter (
56+ self .evaluator , emit_provider_configuration_changed
57+ )
58+
4659 self .connector : FlagStateConnector = (
4760 FileWatcher (
48- self .config , self .flag_store , emit_provider_ready , emit_provider_error
61+ self .config ,
62+ flag_store_adapter , # type: ignore[arg-type]
63+ emit_provider_ready ,
64+ emit_provider_error ,
4965 )
5066 if self .config .offline_flag_source_path
5167 else GrpcWatcher (
5268 self .config ,
53- self . flag_store ,
69+ flag_store_adapter , # type: ignore[arg-type]
5470 emit_provider_ready ,
5571 emit_provider_error ,
5672 emit_provider_stale ,
@@ -69,34 +85,39 @@ def resolve_boolean_details(
6985 default_value : bool ,
7086 evaluation_context : EvaluationContext | None = None ,
7187 ) -> FlagResolutionDetails [bool ]:
72- return self ._resolve (key , default_value , evaluation_context )
88+ return self .evaluator .resolve_boolean_value (
89+ key , default_value , evaluation_context
90+ )
7391
7492 def resolve_string_details (
7593 self ,
7694 key : str ,
7795 default_value : str ,
7896 evaluation_context : EvaluationContext | None = None ,
7997 ) -> FlagResolutionDetails [str ]:
80- return self ._resolve (key , default_value , evaluation_context )
98+ return self .evaluator .resolve_string_value (
99+ key , default_value , evaluation_context
100+ )
81101
82102 def resolve_float_details (
83103 self ,
84104 key : str ,
85105 default_value : float ,
86106 evaluation_context : EvaluationContext | None = None ,
87107 ) -> FlagResolutionDetails [float ]:
88- result = self ._resolve (key , default_value , evaluation_context )
89- if isinstance (result .value , int ):
90- result .value = float (result .value )
91- return result
108+ return self .evaluator .resolve_float_value (
109+ key , default_value , evaluation_context
110+ )
92111
93112 def resolve_integer_details (
94113 self ,
95114 key : str ,
96115 default_value : int ,
97116 evaluation_context : EvaluationContext | None = None ,
98117 ) -> FlagResolutionDetails [int ]:
99- return self ._resolve (key , default_value , evaluation_context )
118+ return self .evaluator .resolve_integer_value (
119+ key , default_value , evaluation_context
120+ )
100121
101122 def resolve_object_details (
102123 self ,
@@ -107,75 +128,6 @@ def resolve_object_details(
107128 ) -> FlagResolutionDetails [
108129 typing .Sequence [FlagValueType ] | typing .Mapping [str , FlagValueType ]
109130 ]:
110- return self ._resolve (key , default_value , evaluation_context )
111-
112- def _resolve (
113- self ,
114- key : str ,
115- default_value : T ,
116- evaluation_context : EvaluationContext | None = None ,
117- ) -> FlagResolutionDetails [T ]:
118- flag = self .flag_store .get_flag (key )
119- if not flag :
120- raise FlagNotFoundError (f"Flag with key { key } not present in flag store." )
121-
122- metadata = _merge_metadata (flag .metadata , self .flag_store .flag_set_metadata )
123-
124- if flag .state == "DISABLED" :
125- return FlagResolutionDetails (
126- default_value , flag_metadata = metadata , reason = Reason .DISABLED
127- )
128-
129- if not flag .targeting :
130- return _default_resolve (flag , metadata , Reason .STATIC , default_value )
131-
132- try :
133- variant = targeting (flag .key , flag .targeting , evaluation_context )
134- if variant is None :
135- return _default_resolve (flag , metadata , Reason .DEFAULT , default_value )
136-
137- # convert to string to support shorthand (boolean in python is with capital T hence the special case)
138- if isinstance (variant , bool ):
139- variant = str (variant ).lower ()
140- elif not isinstance (variant , str ):
141- variant = str (variant )
142-
143- if variant not in flag .variants :
144- raise GeneralError (
145- f"Resolved variant { variant } not in variants config."
146- )
147-
148- except ReferenceError :
149- raise ParseError (f"Invalid targeting { targeting } " ) from ReferenceError
150-
151- variant , value = flag .get_variant (variant )
152- if value is None :
153- raise GeneralError (f"Resolved variant { variant } not in variants config." )
154-
155- return FlagResolutionDetails (
156- value ,
157- variant = variant ,
158- reason = Reason .TARGETING_MATCH ,
159- flag_metadata = metadata ,
160- )
161-
162-
163- def _default_resolve (
164- flag : Flag ,
165- metadata : typing .Mapping [str , float | int | str | bool ],
166- reason : Reason ,
167- default_value : typing .Any = None ,
168- ) -> FlagResolutionDetails :
169- variant , value = flag .default
170- if variant is None :
171- return FlagResolutionDetails (
172- default_value ,
173- variant = variant ,
174- reason = Reason .DEFAULT ,
175- flag_metadata = metadata ,
131+ return self .evaluator .resolve_object_value (
132+ key , default_value , evaluation_context
176133 )
177- if variant not in flag .variants :
178- raise GeneralError (f"Resolved variant { variant } not in variants config." )
179- return FlagResolutionDetails (
180- value , variant = variant , flag_metadata = metadata , reason = reason
181- )
0 commit comments