Skip to content

Commit cf51d68

Browse files
committed
feat(flagd): Context value hydration
Signed-off-by: Simon Schrottner <[email protected]>
1 parent a8990b8 commit cf51d68

File tree

9 files changed

+52
-8
lines changed

9 files changed

+52
-8
lines changed

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@
2525
import warnings
2626

2727
from openfeature.evaluation_context import EvaluationContext
28+
from openfeature.event import ProviderEventDetails
2829
from openfeature.flag_evaluation import FlagResolutionDetails
30+
from openfeature.hook import Hook
2931
from openfeature.provider import AbstractProvider
3032
from openfeature.provider.metadata import Metadata
3133

3234
from .config import CacheType, Config, ResolverType
3335
from .resolvers import AbstractResolver, GrpcResolver, InProcessResolver
36+
from .sync_metadata_hook import SyncMetadataHook
3437

3538
T = typing.TypeVar("T")
3639

@@ -96,8 +99,16 @@ def __init__( # noqa: PLR0913
9699
max_cache_size=max_cache_size,
97100
cert_path=cert_path,
98101
)
102+
self.enriched_context: dict = {}
99103

100104
self.resolver = self.setup_resolver()
105+
self.hooks: list[Hook] = [SyncMetadataHook(self.get_enriched_context)]
106+
107+
def get_enriched_context(self) -> EvaluationContext:
108+
return EvaluationContext(attributes=self.enriched_context)
109+
110+
def get_provider_hooks(self) -> list[Hook]:
111+
return self.hooks
101112

102113
def setup_resolver(self) -> AbstractResolver:
103114
if self.config.resolver == ResolverType.RPC:
@@ -114,7 +125,7 @@ def setup_resolver(self) -> AbstractResolver:
114125
):
115126
return InProcessResolver(
116127
self.config,
117-
self.emit_provider_ready,
128+
self.on_provider_ready,
118129
self.emit_provider_error,
119130
self.emit_provider_stale,
120131
self.emit_provider_configuration_changed,
@@ -184,3 +195,8 @@ def resolve_object_details(
184195
return self.resolver.resolve_object_details(
185196
key, default_value, evaluation_context
186197
)
198+
199+
def on_provider_ready(self, details: ProviderEventDetails, metadata: dict) -> None:
200+
self.enriched_context = metadata
201+
self.emit_provider_ready(details)
202+
pass

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def _resolve( # noqa: PLR0915 C901
276276
return cached_flag
277277

278278
context = self._convert_context(evaluation_context)
279-
call_args = {"timeout": self.deadline}
279+
call_args = {"timeout": self.deadline, "wait_for_ready": True}
280280
try:
281281
request: Message
282282
if flag_type == FlagType.BOOLEAN:

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class InProcessResolver:
2121
def __init__(
2222
self,
2323
config: Config,
24-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
24+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
2525
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
2626
emit_provider_stale: typing.Callable[[ProviderEventDetails], None],
2727
emit_provider_configuration_changed: typing.Callable[

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
self,
2525
config: Config,
2626
flag_store: FlagStore,
27-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
27+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
2828
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
2929
):
3030
if config.offline_flag_source_path is None:
@@ -94,7 +94,8 @@ def _load_data(self, modified_time: typing.Optional[float] = None) -> None:
9494
self.emit_provider_ready(
9595
ProviderEventDetails(
9696
message="Reloading file contents recovered from error state"
97-
)
97+
),
98+
{},
9899
)
99100
self.should_emit_ready_on_success = False
100101

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import typing
66

77
import grpc
8+
from google.protobuf.json_format import MessageToDict
89

910
from openfeature.evaluation_context import EvaluationContext
1011
from openfeature.event import ProviderEventDetails
@@ -26,7 +27,7 @@ def __init__(
2627
self,
2728
config: Config,
2829
flag_store: FlagStore,
29-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
30+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
3031
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
3132
emit_provider_stale: typing.Callable[[ProviderEventDetails], None],
3233
):
@@ -157,6 +158,12 @@ def listen(self) -> None:
157158

158159
while self.active:
159160
try:
161+
context_values_request = sync_pb2.GetMetadataRequest()
162+
context_values_response: sync_pb2.GetMetadataResponse = (
163+
self.stub.GetMetadata(context_values_request, wait_for_ready=True)
164+
)
165+
context_values = MessageToDict(context_values_response)
166+
160167
request = sync_pb2.SyncFlagsRequest(**request_args)
161168

162169
logger.debug("Setting up gRPC sync flags connection")
@@ -173,7 +180,8 @@ def listen(self) -> None:
173180
self.emit_provider_ready(
174181
ProviderEventDetails(
175182
message="gRPC sync connection established"
176-
)
183+
),
184+
context_values["metadata"],
177185
)
178186
self.connected = True
179187

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import typing
2+
3+
from openfeature.evaluation_context import EvaluationContext
4+
from openfeature.hook import Hook, HookContext, HookHints
5+
6+
7+
class SyncMetadataHook(Hook):
8+
def __init__(self, context_supplier: typing.Callable[[], EvaluationContext]):
9+
self.context_supplier = context_supplier
10+
11+
def before(
12+
self, hook_context: HookContext, hints: HookHints
13+
) -> typing.Optional[EvaluationContext]:
14+
return self.context_supplier()

providers/openfeature-provider-flagd/tests/e2e/file/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"~sync",
1313
"~caching",
1414
"~grace",
15+
"~contextEnrichment",
1516
}
1617

1718

providers/openfeature-provider-flagd/tests/e2e/step/flag_step.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
import requests
24
from asserts import assert_equal
35
from pytest_bdd import given, parsers, then, when
@@ -73,6 +75,8 @@ def resolve_details_value(
7375
value: str,
7476
):
7577
_, _, type_info = key_and_default_and_type
78+
logging.warn(details.reason)
79+
logging.warn(details.error_message)
7680
assert_equal(details.value, type_cast[type_info](value))
7781

7882

0 commit comments

Comments
 (0)