|
| 1 | +import json |
1 | 2 | import logging
|
2 | 3 | import threading
|
3 | 4 | import time
|
|
39 | 40 |
|
40 | 41 | class GrpcResolver:
|
41 | 42 | def __init__(
|
42 |
| - self, |
43 |
| - config: Config, |
44 |
| - emit_provider_ready: typing.Callable[[ProviderEventDetails], None], |
45 |
| - emit_provider_error: typing.Callable[[ProviderEventDetails], None], |
46 |
| - emit_provider_stale: typing.Callable[[ProviderEventDetails], None], |
47 |
| - emit_provider_configuration_changed: typing.Callable[ |
48 |
| - [ProviderEventDetails], None |
49 |
| - ], |
| 43 | + self, |
| 44 | + config: Config, |
| 45 | + emit_provider_ready: typing.Callable[[ProviderEventDetails], None], |
| 46 | + emit_provider_error: typing.Callable[[ProviderEventDetails], None], |
| 47 | + emit_provider_stale: typing.Callable[[ProviderEventDetails], None], |
| 48 | + emit_provider_configuration_changed: typing.Callable[ |
| 49 | + [ProviderEventDetails], None |
| 50 | + ], |
50 | 51 | ):
|
51 | 52 | self.active = False
|
52 | 53 | self.config = config
|
@@ -80,6 +81,43 @@ def _generate_channel(self, config: Config) -> grpc.Channel:
|
80 | 81 | ("grpc.initial_reconnect_backoff_ms", config.retry_backoff_ms),
|
81 | 82 | ("grpc.max_reconnect_backoff_ms", config.retry_backoff_max_ms),
|
82 | 83 | ("grpc.min_reconnect_backoff_ms", config.deadline_ms),
|
| 84 | + ("grpc.service_config", json.dumps({ |
| 85 | + "methodConfig": [ |
| 86 | + { |
| 87 | + "name": [ |
| 88 | + { |
| 89 | + "service": "flagd.sync.v1.FlagSyncService" |
| 90 | + }, |
| 91 | + { |
| 92 | + "service": "flagd.evaluation.v1.Service" |
| 93 | + } |
| 94 | + ], |
| 95 | + "retryPolicy": { |
| 96 | + "maxAttempts": 3.0, |
| 97 | + "initialBackoff": "1s", |
| 98 | + "maxBackoff": "5s", |
| 99 | + "backoffMultiplier": 2.0, |
| 100 | + "retryableStatusCodes": [ |
| 101 | + "CANCELLED", |
| 102 | + "UNKNOWN", |
| 103 | + "INVALID_ARGUMENT", |
| 104 | + "NOT_FOUND", |
| 105 | + "ALREADY_EXISTS", |
| 106 | + "PERMISSION_DENIED", |
| 107 | + "RESOURCE_EXHAUSTED", |
| 108 | + "FAILED_PRECONDITION", |
| 109 | + "ABORTED", |
| 110 | + "OUT_OF_RANGE", |
| 111 | + "UNIMPLEMENTED", |
| 112 | + "INTERNAL", |
| 113 | + "UNAVAILABLE", |
| 114 | + "DATA_LOSS", |
| 115 | + "UNAUTHENTICATED" |
| 116 | + ] |
| 117 | + } |
| 118 | + } |
| 119 | + ] |
| 120 | + })) |
83 | 121 | ]
|
84 | 122 | if config.tls:
|
85 | 123 | channel_args = {
|
@@ -138,8 +176,8 @@ def monitor(self) -> None:
|
138 | 176 | def _state_change_callback(self, new_state: ChannelConnectivity) -> None:
|
139 | 177 | logger.debug(f"gRPC state change: {new_state}")
|
140 | 178 | if (
|
141 |
| - new_state == grpc.ChannelConnectivity.READY |
142 |
| - or new_state == grpc.ChannelConnectivity.IDLE |
| 179 | + new_state == grpc.ChannelConnectivity.READY |
| 180 | + or new_state == grpc.ChannelConnectivity.IDLE |
143 | 181 | ):
|
144 | 182 | if not self.thread or not self.thread.is_alive():
|
145 | 183 | self.thread = threading.Thread(
|
@@ -193,7 +231,7 @@ def listen(self) -> None:
|
193 | 231 | try:
|
194 | 232 | logger.debug("Setting up gRPC sync flags connection")
|
195 | 233 | for message in self.stub.EventStream(
|
196 |
| - request, wait_for_ready=True, **call_args |
| 234 | + request, wait_for_ready=True, **call_args |
197 | 235 | ):
|
198 | 236 | if message.type == "provider_ready":
|
199 | 237 | self.emit_provider_ready(
|
@@ -227,51 +265,51 @@ def handle_changed_flags(self, data: typing.Any) -> None:
|
227 | 265 | self.emit_provider_configuration_changed(ProviderEventDetails(changed_flags))
|
228 | 266 |
|
229 | 267 | def resolve_boolean_details(
|
230 |
| - self, |
231 |
| - key: str, |
232 |
| - default_value: bool, |
233 |
| - evaluation_context: typing.Optional[EvaluationContext] = None, |
| 268 | + self, |
| 269 | + key: str, |
| 270 | + default_value: bool, |
| 271 | + evaluation_context: typing.Optional[EvaluationContext] = None, |
234 | 272 | ) -> FlagResolutionDetails[bool]:
|
235 | 273 | return self._resolve(key, FlagType.BOOLEAN, default_value, evaluation_context)
|
236 | 274 |
|
237 | 275 | def resolve_string_details(
|
238 |
| - self, |
239 |
| - key: str, |
240 |
| - default_value: str, |
241 |
| - evaluation_context: typing.Optional[EvaluationContext] = None, |
| 276 | + self, |
| 277 | + key: str, |
| 278 | + default_value: str, |
| 279 | + evaluation_context: typing.Optional[EvaluationContext] = None, |
242 | 280 | ) -> FlagResolutionDetails[str]:
|
243 | 281 | return self._resolve(key, FlagType.STRING, default_value, evaluation_context)
|
244 | 282 |
|
245 | 283 | def resolve_float_details(
|
246 |
| - self, |
247 |
| - key: str, |
248 |
| - default_value: float, |
249 |
| - evaluation_context: typing.Optional[EvaluationContext] = None, |
| 284 | + self, |
| 285 | + key: str, |
| 286 | + default_value: float, |
| 287 | + evaluation_context: typing.Optional[EvaluationContext] = None, |
250 | 288 | ) -> FlagResolutionDetails[float]:
|
251 | 289 | return self._resolve(key, FlagType.FLOAT, default_value, evaluation_context)
|
252 | 290 |
|
253 | 291 | def resolve_integer_details(
|
254 |
| - self, |
255 |
| - key: str, |
256 |
| - default_value: int, |
257 |
| - evaluation_context: typing.Optional[EvaluationContext] = None, |
| 292 | + self, |
| 293 | + key: str, |
| 294 | + default_value: int, |
| 295 | + evaluation_context: typing.Optional[EvaluationContext] = None, |
258 | 296 | ) -> FlagResolutionDetails[int]:
|
259 | 297 | return self._resolve(key, FlagType.INTEGER, default_value, evaluation_context)
|
260 | 298 |
|
261 | 299 | def resolve_object_details(
|
262 |
| - self, |
263 |
| - key: str, |
264 |
| - default_value: typing.Union[dict, list], |
265 |
| - evaluation_context: typing.Optional[EvaluationContext] = None, |
| 300 | + self, |
| 301 | + key: str, |
| 302 | + default_value: typing.Union[dict, list], |
| 303 | + evaluation_context: typing.Optional[EvaluationContext] = None, |
266 | 304 | ) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
267 | 305 | return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
|
268 | 306 |
|
269 | 307 | def _resolve( # noqa: PLR0915 C901
|
270 |
| - self, |
271 |
| - flag_key: str, |
272 |
| - flag_type: FlagType, |
273 |
| - default_value: T, |
274 |
| - evaluation_context: typing.Optional[EvaluationContext], |
| 308 | + self, |
| 309 | + flag_key: str, |
| 310 | + flag_type: FlagType, |
| 311 | + default_value: T, |
| 312 | + evaluation_context: typing.Optional[EvaluationContext], |
275 | 313 | ) -> FlagResolutionDetails[T]:
|
276 | 314 | if self.cache is not None and flag_key in self.cache:
|
277 | 315 | cached_flag: FlagResolutionDetails[T] = self.cache[flag_key]
|
@@ -342,7 +380,7 @@ def _resolve( # noqa: PLR0915 C901
|
342 | 380 | return result
|
343 | 381 |
|
344 | 382 | def _convert_context(
|
345 |
| - self, evaluation_context: typing.Optional[EvaluationContext] |
| 383 | + self, evaluation_context: typing.Optional[EvaluationContext] |
346 | 384 | ) -> Struct:
|
347 | 385 | s = Struct()
|
348 | 386 | if evaluation_context:
|
|
0 commit comments