2323
2424import typing
2525
26- import grpc
27- from google .protobuf .struct_pb2 import Struct
28-
2926from openfeature .evaluation_context import EvaluationContext
30- from openfeature .exception import (
31- FlagNotFoundError ,
32- GeneralError ,
33- InvalidContextError ,
34- ParseError ,
35- TypeMismatchError ,
36- )
3727from openfeature .flag_evaluation import FlagResolutionDetails
3828from openfeature .provider .metadata import Metadata
3929from openfeature .provider .provider import AbstractProvider
4030
41- from .config import Config
42- from .flag_type import FlagType
43- from .proto .schema .v1 import schema_pb2 , schema_pb2_grpc
31+ from .config import Config , ResolverType
32+ from .resolvers import AbstractResolver , GrpcResolver , InProcessResolver
4433
4534T = typing .TypeVar ("T" )
4635
4736
4837class FlagdProvider (AbstractProvider ):
4938 """Flagd OpenFeature Provider"""
5039
51- def __init__ (
40+ def __init__ ( # noqa: PLR0913
5241 self ,
5342 host : typing .Optional [str ] = None ,
5443 port : typing .Optional [int ] = None ,
5544 tls : typing .Optional [bool ] = None ,
5645 timeout : typing .Optional [int ] = None ,
46+ resolver_type : typing .Optional [ResolverType ] = None ,
47+ offline_flag_source_path : typing .Optional [str ] = None ,
48+ offline_poll_interval_seconds : typing .Optional [float ] = None ,
5749 ):
5850 """
5951 Create an instance of the FlagdProvider
@@ -68,14 +60,26 @@ def __init__(
6860 port = port ,
6961 tls = tls ,
7062 timeout = timeout ,
63+ resolver_type = resolver_type ,
64+ offline_flag_source_path = offline_flag_source_path ,
65+ offline_poll_interval_seconds = offline_poll_interval_seconds ,
7166 )
7267
73- channel_factory = grpc .secure_channel if tls else grpc .insecure_channel
74- self .channel = channel_factory (f"{ self .config .host } :{ self .config .port } " )
75- self .stub = schema_pb2_grpc .ServiceStub (self .channel )
68+ self .resolver = self .setup_resolver ()
69+
70+ def setup_resolver (self ) -> AbstractResolver :
71+ if self .config .resolver_type == ResolverType .GRPC :
72+ return GrpcResolver (self .config )
73+ elif self .config .resolver_type == ResolverType .IN_PROCESS :
74+ return InProcessResolver (self .config , self )
75+ else :
76+ raise ValueError (
77+ f"`resolver_type` parameter invalid: { self .config .resolver_type } "
78+ )
7679
7780 def shutdown (self ) -> None :
78- self .channel .close ()
81+ if self .resolver :
82+ self .resolver .shutdown ()
7983
8084 def get_metadata (self ) -> Metadata :
8185 """Returns provider metadata"""
@@ -87,108 +91,46 @@ def resolve_boolean_details(
8791 default_value : bool ,
8892 evaluation_context : typing .Optional [EvaluationContext ] = None ,
8993 ) -> FlagResolutionDetails [bool ]:
90- return self ._resolve (key , FlagType .BOOLEAN , default_value , evaluation_context )
94+ return self .resolver .resolve_boolean_details (
95+ key , default_value , evaluation_context
96+ )
9197
9298 def resolve_string_details (
9399 self ,
94100 key : str ,
95101 default_value : str ,
96102 evaluation_context : typing .Optional [EvaluationContext ] = None ,
97103 ) -> FlagResolutionDetails [str ]:
98- return self ._resolve (key , FlagType .STRING , default_value , evaluation_context )
104+ return self .resolver .resolve_string_details (
105+ key , default_value , evaluation_context
106+ )
99107
100108 def resolve_float_details (
101109 self ,
102110 key : str ,
103111 default_value : float ,
104112 evaluation_context : typing .Optional [EvaluationContext ] = None ,
105113 ) -> FlagResolutionDetails [float ]:
106- return self ._resolve (key , FlagType .FLOAT , default_value , evaluation_context )
114+ return self .resolver .resolve_float_details (
115+ key , default_value , evaluation_context
116+ )
107117
108118 def resolve_integer_details (
109119 self ,
110120 key : str ,
111121 default_value : int ,
112122 evaluation_context : typing .Optional [EvaluationContext ] = None ,
113123 ) -> FlagResolutionDetails [int ]:
114- return self ._resolve (key , FlagType .INTEGER , default_value , evaluation_context )
124+ return self .resolver .resolve_integer_details (
125+ key , default_value , evaluation_context
126+ )
115127
116128 def resolve_object_details (
117129 self ,
118130 key : str ,
119131 default_value : typing .Union [dict , list ],
120132 evaluation_context : typing .Optional [EvaluationContext ] = None ,
121133 ) -> FlagResolutionDetails [typing .Union [dict , list ]]:
122- return self ._resolve (key , FlagType .OBJECT , default_value , evaluation_context )
123-
124- def _resolve (
125- self ,
126- flag_key : str ,
127- flag_type : FlagType ,
128- default_value : T ,
129- evaluation_context : typing .Optional [EvaluationContext ],
130- ) -> FlagResolutionDetails [T ]:
131- context = self ._convert_context (evaluation_context )
132- call_args = {"timeout" : self .config .timeout }
133- try :
134- if flag_type == FlagType .BOOLEAN :
135- request = schema_pb2 .ResolveBooleanRequest ( # type:ignore[attr-defined]
136- flag_key = flag_key , context = context
137- )
138- response = self .stub .ResolveBoolean (request , ** call_args )
139- elif flag_type == FlagType .STRING :
140- request = schema_pb2 .ResolveStringRequest ( # type:ignore[attr-defined]
141- flag_key = flag_key , context = context
142- )
143- response = self .stub .ResolveString (request , ** call_args )
144- elif flag_type == FlagType .OBJECT :
145- request = schema_pb2 .ResolveObjectRequest ( # type:ignore[attr-defined]
146- flag_key = flag_key , context = context
147- )
148- response = self .stub .ResolveObject (request , ** call_args )
149- elif flag_type == FlagType .FLOAT :
150- request = schema_pb2 .ResolveFloatRequest ( # type:ignore[attr-defined]
151- flag_key = flag_key , context = context
152- )
153- response = self .stub .ResolveFloat (request , ** call_args )
154- elif flag_type == FlagType .INTEGER :
155- request = schema_pb2 .ResolveIntRequest ( # type:ignore[attr-defined]
156- flag_key = flag_key , context = context
157- )
158- response = self .stub .ResolveInt (request , ** call_args )
159- else :
160- raise ValueError (f"Unknown flag type: { flag_type } " )
161-
162- except grpc .RpcError as e :
163- code = e .code ()
164- message = f"received grpc status code { code } "
165-
166- if code == grpc .StatusCode .NOT_FOUND :
167- raise FlagNotFoundError (message ) from e
168- elif code == grpc .StatusCode .INVALID_ARGUMENT :
169- raise TypeMismatchError (message ) from e
170- elif code == grpc .StatusCode .DATA_LOSS :
171- raise ParseError (message ) from e
172- raise GeneralError (message ) from e
173-
174- # Got a valid flag and valid type. Return it.
175- return FlagResolutionDetails (
176- value = response .value ,
177- reason = response .reason ,
178- variant = response .variant ,
134+ return self .resolver .resolve_object_details (
135+ key , default_value , evaluation_context
179136 )
180-
181- def _convert_context (
182- self , evaluation_context : typing .Optional [EvaluationContext ]
183- ) -> Struct :
184- s = Struct ()
185- if evaluation_context :
186- try :
187- s ["targetingKey" ] = evaluation_context .targeting_key
188- s .update (evaluation_context .attributes )
189- except ValueError as exc :
190- message = (
191- "could not serialize evaluation context to google.protobuf.Struct"
192- )
193- raise InvalidContextError (message ) from exc
194- return s
0 commit comments