23
23
24
24
import typing
25
25
26
- import grpc
27
- from google .protobuf .struct_pb2 import Struct
28
-
29
26
from openfeature .evaluation_context import EvaluationContext
30
- from openfeature .exception import (
31
- FlagNotFoundError ,
32
- GeneralError ,
33
- InvalidContextError ,
34
- ParseError ,
35
- TypeMismatchError ,
36
- )
37
27
from openfeature .flag_evaluation import FlagResolutionDetails
38
28
from openfeature .provider .metadata import Metadata
39
29
from openfeature .provider .provider import AbstractProvider
40
30
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
44
33
45
34
T = typing .TypeVar ("T" )
46
35
47
36
48
37
class FlagdProvider (AbstractProvider ):
49
38
"""Flagd OpenFeature Provider"""
50
39
51
- def __init__ (
40
+ def __init__ ( # noqa: PLR0913
52
41
self ,
53
42
host : typing .Optional [str ] = None ,
54
43
port : typing .Optional [int ] = None ,
55
44
tls : typing .Optional [bool ] = None ,
56
45
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 ,
57
49
):
58
50
"""
59
51
Create an instance of the FlagdProvider
@@ -68,14 +60,26 @@ def __init__(
68
60
port = port ,
69
61
tls = tls ,
70
62
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 ,
71
66
)
72
67
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
+ )
76
79
77
80
def shutdown (self ) -> None :
78
- self .channel .close ()
81
+ if self .resolver :
82
+ self .resolver .shutdown ()
79
83
80
84
def get_metadata (self ) -> Metadata :
81
85
"""Returns provider metadata"""
@@ -87,108 +91,46 @@ def resolve_boolean_details(
87
91
default_value : bool ,
88
92
evaluation_context : typing .Optional [EvaluationContext ] = None ,
89
93
) -> 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
+ )
91
97
92
98
def resolve_string_details (
93
99
self ,
94
100
key : str ,
95
101
default_value : str ,
96
102
evaluation_context : typing .Optional [EvaluationContext ] = None ,
97
103
) -> 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
+ )
99
107
100
108
def resolve_float_details (
101
109
self ,
102
110
key : str ,
103
111
default_value : float ,
104
112
evaluation_context : typing .Optional [EvaluationContext ] = None ,
105
113
) -> 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
+ )
107
117
108
118
def resolve_integer_details (
109
119
self ,
110
120
key : str ,
111
121
default_value : int ,
112
122
evaluation_context : typing .Optional [EvaluationContext ] = None ,
113
123
) -> 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
+ )
115
127
116
128
def resolve_object_details (
117
129
self ,
118
130
key : str ,
119
131
default_value : typing .Union [dict , list ],
120
132
evaluation_context : typing .Optional [EvaluationContext ] = None ,
121
133
) -> 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
179
136
)
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