openfeature-provider-flagd 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openfeature/contrib/provider/flagd/provider.py +10 -3
- openfeature/contrib/provider/flagd/resolvers/grpc.py +82 -27
- openfeature/contrib/provider/flagd/resolvers/in_process.py +7 -3
- openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +70 -38
- openfeature/contrib/provider/flagd/resolvers/process/targeting.py +5 -1
- openfeature/contrib/provider/flagd/resolvers/protocol.py +7 -3
- openfeature/contrib/provider/flagd/resolvers/types.py +7 -0
- openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +36 -36
- openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +41 -22
- openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +2 -2
- openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +194 -39
- openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +22 -16
- openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +33 -10
- openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +2 -2
- openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +94 -19
- openfeature/schemas/protobuf/schema/v1/schema_pb2.py +4 -4
- openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +25 -19
- openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +2 -2
- openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +194 -39
- openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +4 -4
- openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +9 -9
- openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +2 -2
- openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +69 -14
- openfeature_provider_flagd-0.2.7.dist-info/METADATA +208 -0
- openfeature_provider_flagd-0.2.7.dist-info/RECORD +37 -0
- {openfeature_provider_flagd-0.2.6.dist-info → openfeature_provider_flagd-0.2.7.dist-info}/WHEEL +1 -1
- openfeature_provider_flagd-0.2.7.dist-info/entry_points.txt +6 -0
- {openfeature_provider_flagd-0.2.6.dist-info → openfeature_provider_flagd-0.2.7.dist-info}/licenses/LICENSE +1 -1
- openfeature/.gitignore +0 -2
- openfeature_provider_flagd-0.2.6.dist-info/METADATA +0 -370
- openfeature_provider_flagd-0.2.6.dist-info/RECORD +0 -36
|
@@ -28,7 +28,7 @@ import grpc
|
|
|
28
28
|
|
|
29
29
|
from openfeature.evaluation_context import EvaluationContext
|
|
30
30
|
from openfeature.event import ProviderEventDetails
|
|
31
|
-
from openfeature.flag_evaluation import FlagResolutionDetails
|
|
31
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType
|
|
32
32
|
from openfeature.hook import Hook
|
|
33
33
|
from openfeature.provider import AbstractProvider
|
|
34
34
|
from openfeature.provider.metadata import Metadata
|
|
@@ -75,6 +75,9 @@ class FlagdProvider(AbstractProvider):
|
|
|
75
75
|
:param deadline_ms: the maximum to wait before a request times out
|
|
76
76
|
:param timeout: the maximum time to wait before a request times out
|
|
77
77
|
:param retry_backoff_ms: the number of milliseconds to backoff
|
|
78
|
+
:param selector: filter flag configurations by source (in-process mode only)
|
|
79
|
+
Passed via both flagd-selector gRPC metadata header and request body
|
|
80
|
+
for backward compatibility with all flagd versions.
|
|
78
81
|
:param offline_flag_source_path: the path to the flag source file
|
|
79
82
|
:param stream_deadline_ms: the maximum time to wait before a request times out
|
|
80
83
|
:param keep_alive_time: the number of milliseconds to keep alive
|
|
@@ -199,9 +202,13 @@ class FlagdProvider(AbstractProvider):
|
|
|
199
202
|
def resolve_object_details(
|
|
200
203
|
self,
|
|
201
204
|
flag_key: str,
|
|
202
|
-
default_value: typing.Union[
|
|
205
|
+
default_value: typing.Union[
|
|
206
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
207
|
+
],
|
|
203
208
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
204
|
-
) -> FlagResolutionDetails[
|
|
209
|
+
) -> FlagResolutionDetails[
|
|
210
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
211
|
+
]:
|
|
205
212
|
return self.resolver.resolve_object_details(
|
|
206
213
|
flag_key, default_value, evaluation_context
|
|
207
214
|
)
|
|
@@ -21,7 +21,7 @@ from openfeature.exception import (
|
|
|
21
21
|
ProviderNotReadyError,
|
|
22
22
|
TypeMismatchError,
|
|
23
23
|
)
|
|
24
|
-
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
24
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType, Reason
|
|
25
25
|
from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
|
|
26
26
|
evaluation_pb2,
|
|
27
27
|
evaluation_pb2_grpc,
|
|
@@ -29,6 +29,7 @@ from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
|
|
|
29
29
|
|
|
30
30
|
from ..config import CacheType, Config
|
|
31
31
|
from ..flag_type import FlagType
|
|
32
|
+
from .types import GrpcMultiCallableArgs
|
|
32
33
|
|
|
33
34
|
if typing.TYPE_CHECKING:
|
|
34
35
|
from google.protobuf.message import Message
|
|
@@ -71,8 +72,6 @@ class GrpcResolver:
|
|
|
71
72
|
self.thread: typing.Optional[threading.Thread] = None
|
|
72
73
|
self.timer: typing.Optional[threading.Timer] = None
|
|
73
74
|
|
|
74
|
-
self.start_time = time.time()
|
|
75
|
-
|
|
76
75
|
def _generate_channel(self, config: Config) -> grpc.Channel:
|
|
77
76
|
target = f"{config.host}:{config.port}"
|
|
78
77
|
# Create the channel with the service config
|
|
@@ -121,15 +120,16 @@ class GrpcResolver:
|
|
|
121
120
|
),
|
|
122
121
|
]
|
|
123
122
|
if config.tls:
|
|
124
|
-
|
|
125
|
-
"options": options,
|
|
126
|
-
"credentials": grpc.ssl_channel_credentials(),
|
|
127
|
-
}
|
|
123
|
+
credentials = grpc.ssl_channel_credentials()
|
|
128
124
|
if config.cert_path:
|
|
129
125
|
with open(config.cert_path, "rb") as f:
|
|
130
|
-
|
|
126
|
+
credentials = grpc.ssl_channel_credentials(f.read())
|
|
131
127
|
|
|
132
|
-
channel = grpc.secure_channel(
|
|
128
|
+
channel = grpc.secure_channel(
|
|
129
|
+
target,
|
|
130
|
+
credentials=credentials,
|
|
131
|
+
options=options,
|
|
132
|
+
)
|
|
133
133
|
|
|
134
134
|
else:
|
|
135
135
|
channel = grpc.insecure_channel(
|
|
@@ -161,8 +161,8 @@ class GrpcResolver:
|
|
|
161
161
|
)
|
|
162
162
|
self.monitor_thread.start()
|
|
163
163
|
## block until ready or deadline reached
|
|
164
|
-
timeout = self.deadline + time.
|
|
165
|
-
while not self.connected and time.
|
|
164
|
+
timeout = self.deadline + time.monotonic()
|
|
165
|
+
while not self.connected and time.monotonic() < timeout:
|
|
166
166
|
time.sleep(0.05)
|
|
167
167
|
logger.debug("Finished blocking gRPC state initialization")
|
|
168
168
|
|
|
@@ -199,7 +199,6 @@ class GrpcResolver:
|
|
|
199
199
|
message="gRPC sync disconnected, reconnecting",
|
|
200
200
|
)
|
|
201
201
|
)
|
|
202
|
-
self.start_time = time.time()
|
|
203
202
|
# adding a timer, so we can emit the error event after time
|
|
204
203
|
self.timer = threading.Timer(self.retry_grace_period, self.emit_error)
|
|
205
204
|
|
|
@@ -220,20 +219,16 @@ class GrpcResolver:
|
|
|
220
219
|
|
|
221
220
|
def listen(self) -> None:
|
|
222
221
|
logger.debug("gRPC starting listener thread")
|
|
223
|
-
call_args =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else {}
|
|
227
|
-
)
|
|
222
|
+
call_args: GrpcMultiCallableArgs = {"wait_for_ready": True}
|
|
223
|
+
if self.streamline_deadline_seconds > 0:
|
|
224
|
+
call_args["timeout"] = self.streamline_deadline_seconds
|
|
228
225
|
request = evaluation_pb2.EventStreamRequest()
|
|
229
226
|
|
|
230
227
|
# defining a never ending loop to recreate the stream
|
|
231
228
|
while self.active:
|
|
232
229
|
try:
|
|
233
230
|
logger.debug("Setting up gRPC sync flags connection")
|
|
234
|
-
for message in self.stub.EventStream(
|
|
235
|
-
request, wait_for_ready=True, **call_args
|
|
236
|
-
):
|
|
231
|
+
for message in self.stub.EventStream(request, **call_args):
|
|
237
232
|
if message.type == "provider_ready":
|
|
238
233
|
self.emit_provider_ready(
|
|
239
234
|
ProviderEventDetails(
|
|
@@ -300,25 +295,81 @@ class GrpcResolver:
|
|
|
300
295
|
def resolve_object_details(
|
|
301
296
|
self,
|
|
302
297
|
key: str,
|
|
303
|
-
default_value: typing.Union[
|
|
298
|
+
default_value: typing.Union[
|
|
299
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
300
|
+
],
|
|
304
301
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
305
|
-
) -> FlagResolutionDetails[
|
|
302
|
+
) -> FlagResolutionDetails[
|
|
303
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
304
|
+
]:
|
|
306
305
|
return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
|
|
307
306
|
|
|
307
|
+
@typing.overload
|
|
308
|
+
def _resolve(
|
|
309
|
+
self,
|
|
310
|
+
flag_key: str,
|
|
311
|
+
flag_type: FlagType,
|
|
312
|
+
default_value: bool,
|
|
313
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
314
|
+
) -> FlagResolutionDetails[bool]: ...
|
|
315
|
+
|
|
316
|
+
@typing.overload
|
|
317
|
+
def _resolve(
|
|
318
|
+
self,
|
|
319
|
+
flag_key: str,
|
|
320
|
+
flag_type: FlagType,
|
|
321
|
+
default_value: int,
|
|
322
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
323
|
+
) -> FlagResolutionDetails[int]: ...
|
|
324
|
+
|
|
325
|
+
@typing.overload
|
|
326
|
+
def _resolve(
|
|
327
|
+
self,
|
|
328
|
+
flag_key: str,
|
|
329
|
+
flag_type: FlagType,
|
|
330
|
+
default_value: float,
|
|
331
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
332
|
+
) -> FlagResolutionDetails[float]: ...
|
|
333
|
+
|
|
334
|
+
@typing.overload
|
|
335
|
+
def _resolve(
|
|
336
|
+
self,
|
|
337
|
+
flag_key: str,
|
|
338
|
+
flag_type: FlagType,
|
|
339
|
+
default_value: str,
|
|
340
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
341
|
+
) -> FlagResolutionDetails[str]: ...
|
|
342
|
+
|
|
343
|
+
@typing.overload
|
|
344
|
+
def _resolve(
|
|
345
|
+
self,
|
|
346
|
+
flag_key: str,
|
|
347
|
+
flag_type: FlagType,
|
|
348
|
+
default_value: typing.Union[
|
|
349
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
350
|
+
],
|
|
351
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
352
|
+
) -> FlagResolutionDetails[
|
|
353
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
354
|
+
]: ...
|
|
355
|
+
|
|
308
356
|
def _resolve( # noqa: PLR0915 C901
|
|
309
357
|
self,
|
|
310
358
|
flag_key: str,
|
|
311
359
|
flag_type: FlagType,
|
|
312
|
-
default_value:
|
|
360
|
+
default_value: FlagValueType,
|
|
313
361
|
evaluation_context: typing.Optional[EvaluationContext],
|
|
314
|
-
) -> FlagResolutionDetails[
|
|
362
|
+
) -> FlagResolutionDetails[FlagValueType]:
|
|
315
363
|
if self.cache is not None and flag_key in self.cache:
|
|
316
|
-
cached_flag: FlagResolutionDetails[
|
|
364
|
+
cached_flag: FlagResolutionDetails[FlagValueType] = self.cache[flag_key]
|
|
317
365
|
cached_flag.reason = Reason.CACHED
|
|
318
366
|
return cached_flag
|
|
319
367
|
|
|
320
368
|
context = self._convert_context(evaluation_context)
|
|
321
|
-
call_args = {
|
|
369
|
+
call_args: GrpcMultiCallableArgs = {
|
|
370
|
+
"timeout": self.deadline,
|
|
371
|
+
"wait_for_ready": True,
|
|
372
|
+
}
|
|
322
373
|
try:
|
|
323
374
|
request: Message
|
|
324
375
|
if flag_type == FlagType.BOOLEAN:
|
|
@@ -387,7 +438,11 @@ class GrpcResolver:
|
|
|
387
438
|
if evaluation_context:
|
|
388
439
|
try:
|
|
389
440
|
s["targetingKey"] = evaluation_context.targeting_key
|
|
390
|
-
s.update(
|
|
441
|
+
s.update(
|
|
442
|
+
typing.cast(
|
|
443
|
+
"typing.Mapping[str, typing.Any]", evaluation_context.attributes
|
|
444
|
+
)
|
|
445
|
+
)
|
|
391
446
|
except ValueError as exc:
|
|
392
447
|
message = (
|
|
393
448
|
"could not serialize evaluation context to google.protobuf.Struct"
|
|
@@ -6,7 +6,7 @@ from openfeature.contrib.provider.flagd.resolvers.process.connector.file_watcher
|
|
|
6
6
|
from openfeature.evaluation_context import EvaluationContext
|
|
7
7
|
from openfeature.event import ProviderEventDetails
|
|
8
8
|
from openfeature.exception import ErrorCode, FlagNotFoundError, GeneralError, ParseError
|
|
9
|
-
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
9
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType, Reason
|
|
10
10
|
|
|
11
11
|
from ..config import Config
|
|
12
12
|
from .process.connector import FlagStateConnector
|
|
@@ -105,9 +105,13 @@ class InProcessResolver:
|
|
|
105
105
|
def resolve_object_details(
|
|
106
106
|
self,
|
|
107
107
|
key: str,
|
|
108
|
-
default_value: typing.Union[
|
|
108
|
+
default_value: typing.Union[
|
|
109
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
110
|
+
],
|
|
109
111
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
110
|
-
) -> FlagResolutionDetails[
|
|
112
|
+
) -> FlagResolutionDetails[
|
|
113
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
114
|
+
]:
|
|
111
115
|
return self._resolve(key, default_value, evaluation_context)
|
|
112
116
|
|
|
113
117
|
def _resolve(
|
|
@@ -6,7 +6,7 @@ import typing
|
|
|
6
6
|
|
|
7
7
|
import grpc
|
|
8
8
|
from google.protobuf.json_format import MessageToDict
|
|
9
|
-
from
|
|
9
|
+
from grpc import StatusCode
|
|
10
10
|
|
|
11
11
|
from openfeature.evaluation_context import EvaluationContext
|
|
12
12
|
from openfeature.event import ProviderEventDetails
|
|
@@ -17,6 +17,7 @@ from openfeature.schemas.protobuf.flagd.sync.v1 import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from ....config import Config
|
|
20
|
+
from ...types import GrpcMultiCallableArgs
|
|
20
21
|
from ..connector import FlagStateConnector
|
|
21
22
|
from ..flags import FlagStore
|
|
22
23
|
|
|
@@ -52,8 +53,6 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
52
53
|
self.thread: typing.Optional[threading.Thread] = None
|
|
53
54
|
self.timer: typing.Optional[threading.Timer] = None
|
|
54
55
|
|
|
55
|
-
self.start_time = time.time()
|
|
56
|
-
|
|
57
56
|
def _generate_channel(self, config: Config) -> grpc.Channel:
|
|
58
57
|
target = f"{config.host}:{config.port}"
|
|
59
58
|
# Create the channel with the service config
|
|
@@ -105,22 +104,23 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
105
104
|
options.append(("grpc.default_authority", config.default_authority))
|
|
106
105
|
|
|
107
106
|
if config.channel_credentials is not None:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
channel = grpc.secure_channel(
|
|
108
|
+
target,
|
|
109
|
+
credentials=config.channel_credentials,
|
|
110
|
+
options=options,
|
|
111
|
+
)
|
|
113
112
|
|
|
114
113
|
elif config.tls:
|
|
115
|
-
|
|
116
|
-
"options": options,
|
|
117
|
-
"credentials": grpc.ssl_channel_credentials(),
|
|
118
|
-
}
|
|
114
|
+
credentials = grpc.ssl_channel_credentials()
|
|
119
115
|
if config.cert_path:
|
|
120
116
|
with open(config.cert_path, "rb") as f:
|
|
121
|
-
|
|
117
|
+
credentials = grpc.ssl_channel_credentials(f.read())
|
|
122
118
|
|
|
123
|
-
channel = grpc.secure_channel(
|
|
119
|
+
channel = grpc.secure_channel(
|
|
120
|
+
target,
|
|
121
|
+
credentials=credentials,
|
|
122
|
+
options=options,
|
|
123
|
+
)
|
|
124
124
|
|
|
125
125
|
else:
|
|
126
126
|
channel = grpc.insecure_channel(
|
|
@@ -142,8 +142,8 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
142
142
|
)
|
|
143
143
|
self.monitor_thread.start()
|
|
144
144
|
## block until ready or deadline reached
|
|
145
|
-
timeout = self.deadline + time.
|
|
146
|
-
while not self.connected and time.
|
|
145
|
+
timeout = self.deadline + time.monotonic()
|
|
146
|
+
while not self.connected and time.monotonic() < timeout:
|
|
147
147
|
time.sleep(0.05)
|
|
148
148
|
logger.debug("Finished blocking gRPC state initialization")
|
|
149
149
|
|
|
@@ -180,7 +180,6 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
180
180
|
message="gRPC sync disconnected, reconnecting",
|
|
181
181
|
)
|
|
182
182
|
)
|
|
183
|
-
self.start_time = time.time()
|
|
184
183
|
# adding a timer, so we can emit the error event after time
|
|
185
184
|
self.timer = threading.Timer(self.retry_grace_period, self.emit_error)
|
|
186
185
|
|
|
@@ -203,6 +202,8 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
203
202
|
|
|
204
203
|
def _create_request_args(self) -> dict:
|
|
205
204
|
request_args = {}
|
|
205
|
+
# Pass selector in both request body (legacy) and metadata header (new) for backward compatibility
|
|
206
|
+
# This ensures compatibility with both older and newer flagd versions
|
|
206
207
|
if self.selector is not None:
|
|
207
208
|
request_args["selector"] = self.selector
|
|
208
209
|
if self.provider_id is not None:
|
|
@@ -210,47 +211,68 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
210
211
|
|
|
211
212
|
return request_args
|
|
212
213
|
|
|
214
|
+
def _create_metadata(self) -> typing.Optional[tuple[tuple[str, str]]]:
|
|
215
|
+
"""Create gRPC metadata headers for the request.
|
|
216
|
+
|
|
217
|
+
Returns gRPC metadata as a tuples of tuples containing header key-value pairs.
|
|
218
|
+
The selector is passed via the 'flagd-selector' header per flagd v0.11.0+ specification,
|
|
219
|
+
while also being included in the request body for backward compatibility with older flagd versions.
|
|
220
|
+
"""
|
|
221
|
+
if self.selector is None:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
return (("flagd-selector", self.selector),)
|
|
225
|
+
|
|
226
|
+
def _fetch_metadata(self) -> typing.Optional[sync_pb2.GetMetadataResponse]:
|
|
227
|
+
if self.config.sync_metadata_disabled:
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
context_values_request = sync_pb2.GetMetadataRequest()
|
|
231
|
+
try:
|
|
232
|
+
context_values_response: sync_pb2.GetMetadataResponse = (
|
|
233
|
+
self.stub.GetMetadata(context_values_request, wait_for_ready=True)
|
|
234
|
+
)
|
|
235
|
+
return context_values_response
|
|
236
|
+
except grpc.RpcError as e:
|
|
237
|
+
if e.code() == StatusCode.UNIMPLEMENTED:
|
|
238
|
+
logger.debug(f"Error getting sync metadata: {e}")
|
|
239
|
+
return None
|
|
240
|
+
else:
|
|
241
|
+
raise e
|
|
242
|
+
|
|
213
243
|
def listen(self) -> None:
|
|
214
|
-
call_args = (
|
|
215
|
-
|
|
216
|
-
if self.streamline_deadline_seconds > 0
|
|
217
|
-
else {}
|
|
218
|
-
)
|
|
244
|
+
call_args = self.generate_grpc_call_args()
|
|
245
|
+
|
|
219
246
|
request_args = self._create_request_args()
|
|
220
247
|
|
|
221
248
|
while self.active:
|
|
222
249
|
try:
|
|
223
|
-
context_values_response
|
|
224
|
-
if self.config.sync_metadata_disabled:
|
|
225
|
-
context_values_response = sync_pb2.GetMetadataResponse(
|
|
226
|
-
metadata=Struct()
|
|
227
|
-
)
|
|
228
|
-
else:
|
|
229
|
-
context_values_request = sync_pb2.GetMetadataRequest()
|
|
230
|
-
context_values_response = self.stub.GetMetadata(
|
|
231
|
-
context_values_request, wait_for_ready=True
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
context_values = MessageToDict(context_values_response)
|
|
250
|
+
context_values_response = self._fetch_metadata()
|
|
235
251
|
|
|
236
252
|
request = sync_pb2.SyncFlagsRequest(**request_args)
|
|
237
253
|
|
|
238
254
|
logger.debug("Setting up gRPC sync flags connection")
|
|
239
|
-
for flag_rsp in self.stub.SyncFlags(
|
|
240
|
-
request, wait_for_ready=True, **call_args
|
|
241
|
-
):
|
|
255
|
+
for flag_rsp in self.stub.SyncFlags(request, **call_args):
|
|
242
256
|
flag_str = flag_rsp.flag_configuration
|
|
243
257
|
logger.debug(
|
|
244
258
|
f"Received flag configuration - {abs(hash(flag_str)) % (10**8)}"
|
|
245
259
|
)
|
|
246
260
|
self.flag_store.update(json.loads(flag_str))
|
|
247
261
|
|
|
262
|
+
context_values = {}
|
|
263
|
+
if flag_rsp.sync_context:
|
|
264
|
+
context_values = MessageToDict(flag_rsp.sync_context)
|
|
265
|
+
elif context_values_response:
|
|
266
|
+
context_values = MessageToDict(context_values_response)[
|
|
267
|
+
"metadata"
|
|
268
|
+
]
|
|
269
|
+
|
|
248
270
|
if not self.connected:
|
|
249
271
|
self.emit_provider_ready(
|
|
250
272
|
ProviderEventDetails(
|
|
251
273
|
message="gRPC sync connection established"
|
|
252
274
|
),
|
|
253
|
-
context_values
|
|
275
|
+
context_values,
|
|
254
276
|
)
|
|
255
277
|
self.connected = True
|
|
256
278
|
|
|
@@ -267,3 +289,13 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
267
289
|
logger.exception(
|
|
268
290
|
f"Could not parse flag data using flagd syntax: {flag_str=}"
|
|
269
291
|
)
|
|
292
|
+
|
|
293
|
+
def generate_grpc_call_args(self) -> GrpcMultiCallableArgs:
|
|
294
|
+
call_args: GrpcMultiCallableArgs = {"wait_for_ready": True}
|
|
295
|
+
if self.streamline_deadline_seconds > 0:
|
|
296
|
+
call_args["timeout"] = self.streamline_deadline_seconds
|
|
297
|
+
# Add selector via gRPC metadata header (flagd v0.11.0+ preferred approach)
|
|
298
|
+
metadata = self._create_metadata()
|
|
299
|
+
if metadata is not None:
|
|
300
|
+
call_args["metadata"] = metadata
|
|
301
|
+
return call_args
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
import typing
|
|
3
5
|
|
|
@@ -31,7 +33,9 @@ def targeting(
|
|
|
31
33
|
if not isinstance(targeting, dict):
|
|
32
34
|
raise ParseError(f"Invalid 'targeting' value in flag: {targeting}")
|
|
33
35
|
|
|
34
|
-
json_logic_context
|
|
36
|
+
json_logic_context: dict[str, typing.Any] = (
|
|
37
|
+
dict(evaluation_context.attributes) if evaluation_context else {}
|
|
38
|
+
)
|
|
35
39
|
json_logic_context["$flagd"] = {"flagKey": key, "timestamp": int(time.time())}
|
|
36
40
|
json_logic_context["targetingKey"] = (
|
|
37
41
|
evaluation_context.targeting_key if evaluation_context else None
|
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
from typing_extensions import Protocol
|
|
4
4
|
|
|
5
5
|
from openfeature.evaluation_context import EvaluationContext
|
|
6
|
-
from openfeature.flag_evaluation import FlagResolutionDetails
|
|
6
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class AbstractResolver(Protocol):
|
|
@@ -42,6 +42,10 @@ class AbstractResolver(Protocol):
|
|
|
42
42
|
def resolve_object_details(
|
|
43
43
|
self,
|
|
44
44
|
key: str,
|
|
45
|
-
default_value: typing.Union[
|
|
45
|
+
default_value: typing.Union[
|
|
46
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
47
|
+
],
|
|
46
48
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
47
|
-
) -> FlagResolutionDetails[
|
|
49
|
+
) -> FlagResolutionDetails[
|
|
50
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
51
|
+
]: ...
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
11
|
from google.protobuf.internal import builder as _builder
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
17
|
'',
|
|
18
18
|
'openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto'
|
|
19
19
|
)
|
|
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
|
|
|
25
25
|
from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nAopenfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto\x12\x13\x66lagd.evaluation.v1\x1a\x1cgoogle/protobuf/struct.proto\"=\n\x11ResolveAllRequest\x12(\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"\
|
|
28
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nAopenfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto\x12\x13\x66lagd.evaluation.v1\x1a\x1cgoogle/protobuf/struct.proto\"=\n\x11ResolveAllRequest\x12(\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xce\x01\n\x12ResolveAllResponse\x12\x41\n\x05\x66lags\x18\x01 \x03(\x0b\x32\x32.flagd.evaluation.v1.ResolveAllResponse.FlagsEntry\x12)\n\x08metadata\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x1aJ\n\nFlagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x1c.flagd.evaluation.v1.AnyFlag:\x02\x38\x01\"\xd5\x01\n\x07\x41nyFlag\x12\x0e\n\x06reason\x18\x01 \x01(\t\x12\x0f\n\x07variant\x18\x02 \x01(\t\x12\x14\n\nbool_value\x18\x03 \x01(\x08H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x16\n\x0c\x64ouble_value\x18\x05 \x01(\x01H\x00\x12/\n\x0cobject_value\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12)\n\x08metadata\x18\x07 \x01(\x0b\x32\x17.google.protobuf.StructB\x07\n\x05value\"S\n\x15ResolveBooleanRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"s\n\x16ResolveBooleanResponse\x12\r\n\x05value\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"R\n\x14ResolveStringRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"r\n\x15ResolveStringResponse\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"Q\n\x13ResolveFloatRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"q\n\x14ResolveFloatResponse\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"O\n\x11ResolveIntRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"o\n\x12ResolveIntResponse\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"R\n\x14ResolveObjectRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x8b\x01\n\x15ResolveObjectResponse\x12&\n\x05value\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"J\n\x13\x45ventStreamResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\x12%\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x14\n\x12\x45ventStreamRequest2\xd9\x05\n\x07Service\x12_\n\nResolveAll\x12&.flagd.evaluation.v1.ResolveAllRequest\x1a\'.flagd.evaluation.v1.ResolveAllResponse\"\x00\x12k\n\x0eResolveBoolean\x12*.flagd.evaluation.v1.ResolveBooleanRequest\x1a+.flagd.evaluation.v1.ResolveBooleanResponse\"\x00\x12h\n\rResolveString\x12).flagd.evaluation.v1.ResolveStringRequest\x1a*.flagd.evaluation.v1.ResolveStringResponse\"\x00\x12\x65\n\x0cResolveFloat\x12(.flagd.evaluation.v1.ResolveFloatRequest\x1a).flagd.evaluation.v1.ResolveFloatResponse\"\x00\x12_\n\nResolveInt\x12&.flagd.evaluation.v1.ResolveIntRequest\x1a\'.flagd.evaluation.v1.ResolveIntResponse\"\x00\x12h\n\rResolveObject\x12).flagd.evaluation.v1.ResolveObjectRequest\x1a*.flagd.evaluation.v1.ResolveObjectResponse\"\x00\x12\x64\n\x0b\x45ventStream\x12\'.flagd.evaluation.v1.EventStreamRequest\x1a(.flagd.evaluation.v1.EventStreamResponse\"\x00\x30\x01\x42\xc6\x01\n%dev.openfeature.flagd.grpc.evaluationZ\x13\x66lagd/evaluation/v1\xaa\x02!OpenFeature.Flagd.Grpc.Evaluation\xca\x02\x32OpenFeature\\Providers\\Flagd\\Schema\\Grpc\\Evaluation\xea\x02.OpenFeature::Flagd::Provider::Grpc::Evaluationb\x06proto3')
|
|
29
29
|
|
|
30
30
|
_globals = globals()
|
|
31
31
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -38,35 +38,35 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
38
38
|
_globals['_RESOLVEALLREQUEST']._serialized_start=120
|
|
39
39
|
_globals['_RESOLVEALLREQUEST']._serialized_end=181
|
|
40
40
|
_globals['_RESOLVEALLRESPONSE']._serialized_start=184
|
|
41
|
-
_globals['_RESOLVEALLRESPONSE']._serialized_end=
|
|
42
|
-
_globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_start=
|
|
43
|
-
_globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_end=
|
|
44
|
-
_globals['_ANYFLAG']._serialized_start=
|
|
45
|
-
_globals['_ANYFLAG']._serialized_end=
|
|
46
|
-
_globals['_RESOLVEBOOLEANREQUEST']._serialized_start=
|
|
47
|
-
_globals['_RESOLVEBOOLEANREQUEST']._serialized_end=
|
|
48
|
-
_globals['_RESOLVEBOOLEANRESPONSE']._serialized_start=
|
|
49
|
-
_globals['_RESOLVEBOOLEANRESPONSE']._serialized_end=
|
|
50
|
-
_globals['_RESOLVESTRINGREQUEST']._serialized_start=
|
|
51
|
-
_globals['_RESOLVESTRINGREQUEST']._serialized_end=
|
|
52
|
-
_globals['_RESOLVESTRINGRESPONSE']._serialized_start=
|
|
53
|
-
_globals['_RESOLVESTRINGRESPONSE']._serialized_end=
|
|
54
|
-
_globals['_RESOLVEFLOATREQUEST']._serialized_start=
|
|
55
|
-
_globals['_RESOLVEFLOATREQUEST']._serialized_end=
|
|
56
|
-
_globals['_RESOLVEFLOATRESPONSE']._serialized_start=
|
|
57
|
-
_globals['_RESOLVEFLOATRESPONSE']._serialized_end=
|
|
58
|
-
_globals['_RESOLVEINTREQUEST']._serialized_start=
|
|
59
|
-
_globals['_RESOLVEINTREQUEST']._serialized_end=
|
|
60
|
-
_globals['_RESOLVEINTRESPONSE']._serialized_start=
|
|
61
|
-
_globals['_RESOLVEINTRESPONSE']._serialized_end=
|
|
62
|
-
_globals['_RESOLVEOBJECTREQUEST']._serialized_start=
|
|
63
|
-
_globals['_RESOLVEOBJECTREQUEST']._serialized_end=
|
|
64
|
-
_globals['_RESOLVEOBJECTRESPONSE']._serialized_start=
|
|
65
|
-
_globals['_RESOLVEOBJECTRESPONSE']._serialized_end=
|
|
66
|
-
_globals['_EVENTSTREAMRESPONSE']._serialized_start=
|
|
67
|
-
_globals['_EVENTSTREAMRESPONSE']._serialized_end=
|
|
68
|
-
_globals['_EVENTSTREAMREQUEST']._serialized_start=
|
|
69
|
-
_globals['_EVENTSTREAMREQUEST']._serialized_end=
|
|
70
|
-
_globals['_SERVICE']._serialized_start=
|
|
71
|
-
_globals['_SERVICE']._serialized_end=
|
|
41
|
+
_globals['_RESOLVEALLRESPONSE']._serialized_end=390
|
|
42
|
+
_globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_start=316
|
|
43
|
+
_globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_end=390
|
|
44
|
+
_globals['_ANYFLAG']._serialized_start=393
|
|
45
|
+
_globals['_ANYFLAG']._serialized_end=606
|
|
46
|
+
_globals['_RESOLVEBOOLEANREQUEST']._serialized_start=608
|
|
47
|
+
_globals['_RESOLVEBOOLEANREQUEST']._serialized_end=691
|
|
48
|
+
_globals['_RESOLVEBOOLEANRESPONSE']._serialized_start=693
|
|
49
|
+
_globals['_RESOLVEBOOLEANRESPONSE']._serialized_end=808
|
|
50
|
+
_globals['_RESOLVESTRINGREQUEST']._serialized_start=810
|
|
51
|
+
_globals['_RESOLVESTRINGREQUEST']._serialized_end=892
|
|
52
|
+
_globals['_RESOLVESTRINGRESPONSE']._serialized_start=894
|
|
53
|
+
_globals['_RESOLVESTRINGRESPONSE']._serialized_end=1008
|
|
54
|
+
_globals['_RESOLVEFLOATREQUEST']._serialized_start=1010
|
|
55
|
+
_globals['_RESOLVEFLOATREQUEST']._serialized_end=1091
|
|
56
|
+
_globals['_RESOLVEFLOATRESPONSE']._serialized_start=1093
|
|
57
|
+
_globals['_RESOLVEFLOATRESPONSE']._serialized_end=1206
|
|
58
|
+
_globals['_RESOLVEINTREQUEST']._serialized_start=1208
|
|
59
|
+
_globals['_RESOLVEINTREQUEST']._serialized_end=1287
|
|
60
|
+
_globals['_RESOLVEINTRESPONSE']._serialized_start=1289
|
|
61
|
+
_globals['_RESOLVEINTRESPONSE']._serialized_end=1400
|
|
62
|
+
_globals['_RESOLVEOBJECTREQUEST']._serialized_start=1402
|
|
63
|
+
_globals['_RESOLVEOBJECTREQUEST']._serialized_end=1484
|
|
64
|
+
_globals['_RESOLVEOBJECTRESPONSE']._serialized_start=1487
|
|
65
|
+
_globals['_RESOLVEOBJECTRESPONSE']._serialized_end=1626
|
|
66
|
+
_globals['_EVENTSTREAMRESPONSE']._serialized_start=1628
|
|
67
|
+
_globals['_EVENTSTREAMRESPONSE']._serialized_end=1702
|
|
68
|
+
_globals['_EVENTSTREAMREQUEST']._serialized_start=1704
|
|
69
|
+
_globals['_EVENTSTREAMREQUEST']._serialized_end=1724
|
|
70
|
+
_globals['_SERVICE']._serialized_start=1727
|
|
71
|
+
_globals['_SERVICE']._serialized_end=2456
|
|
72
72
|
# @@protoc_insertion_point(module_scope)
|