From 41ec424562bc7c2939f12158ebca788ee8e1e8cf Mon Sep 17 00:00:00 2001 From: Cole Bailey Date: Thu, 20 Jun 2024 21:35:04 +0200 Subject: [PATCH] add: callback layer between provider and flag store --- .../contrib/provider/flagd/provider.py | 6 +++ .../provider/flagd/resolvers/__init__.py | 51 +------------------ .../contrib/provider/flagd/resolvers/grpc.py | 2 +- .../provider/flagd/resolvers/in_process.py | 6 +-- .../provider/flagd/resolvers/process/flags.py | 10 ++-- .../provider/flagd/resolvers/protocol.py | 48 +++++++++++++++++ 6 files changed, 62 insertions(+), 61 deletions(-) create mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/protocol.py diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py index 15c86f67..28b47ee7 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py @@ -24,6 +24,7 @@ import typing from openfeature.evaluation_context import EvaluationContext +from openfeature.event import ProviderEventDetails from openfeature.flag_evaluation import FlagResolutionDetails from openfeature.provider.metadata import Metadata from openfeature.provider.provider import AbstractProvider @@ -92,6 +93,11 @@ def get_metadata(self) -> Metadata: """Returns provider metadata""" return Metadata(name="FlagdProvider") + def flag_store_updated_callback(self, flag_keys: typing.List[str]) -> None: + self.emit_provider_configuration_changed( + ProviderEventDetails(flags_changed=flag_keys) + ) + def resolve_boolean_details( self, key: str, diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py index 1b77c0c8..f539de8f 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py @@ -1,54 +1,5 @@ -import typing - -from typing_extensions import Protocol - -from openfeature.evaluation_context import EvaluationContext -from openfeature.flag_evaluation import FlagResolutionDetails - from .grpc import GrpcResolver from .in_process import InProcessResolver - - -class AbstractResolver(Protocol): - def initialize(self, evaluation_context: EvaluationContext) -> None: - return - - def shutdown(self) -> None: ... - - def resolve_boolean_details( - self, - key: str, - default_value: bool, - evaluation_context: typing.Optional[EvaluationContext] = None, - ) -> FlagResolutionDetails[bool]: ... - - def resolve_string_details( - self, - key: str, - default_value: str, - evaluation_context: typing.Optional[EvaluationContext] = None, - ) -> FlagResolutionDetails[str]: ... - - def resolve_float_details( - self, - key: str, - default_value: float, - evaluation_context: typing.Optional[EvaluationContext] = None, - ) -> FlagResolutionDetails[float]: ... - - def resolve_integer_details( - self, - key: str, - default_value: int, - evaluation_context: typing.Optional[EvaluationContext] = None, - ) -> FlagResolutionDetails[int]: ... - - def resolve_object_details( - self, - key: str, - default_value: typing.Union[dict, list], - evaluation_context: typing.Optional[EvaluationContext] = None, - ) -> FlagResolutionDetails[typing.Union[dict, list]]: ... - +from .protocol import AbstractResolver __all__ = ["AbstractResolver", "GrpcResolver", "InProcessResolver"] diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py index c2f98522..ca697cf3 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py @@ -16,7 +16,7 @@ from ..config import Config from ..flag_type import FlagType from ..proto.schema.v1 import schema_pb2, schema_pb2_grpc -from . import AbstractResolver +from .protocol import AbstractResolver T = typing.TypeVar("T") diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index e0ea96ba..b9d454ac 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -6,9 +6,9 @@ from openfeature.evaluation_context import EvaluationContext from openfeature.exception import FlagNotFoundError, ParseError from openfeature.flag_evaluation import FlagResolutionDetails, Reason -from openfeature.provider.provider import AbstractProvider from ..config import Config +from ..provider import FlagdProvider from .process.connector import FlagStateConnector from .process.connector.file_watcher import FileWatcher from .process.connector.grpc_watcher import GrpcWatcher @@ -27,10 +27,10 @@ class InProcessResolver: "sem_ver": sem_ver, } - def __init__(self, config: Config, provider: AbstractProvider): + def __init__(self, config: Config, provider: FlagdProvider): self.config = config self.provider = provider - self.flag_store = FlagStore(provider) + self.flag_store = FlagStore(provider.flag_store_updated_callback) self.connector: FlagStateConnector = ( FileWatcher( self.config.offline_flag_source_path, diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py index c8fb6181..4cbfdcec 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py @@ -3,17 +3,15 @@ import typing from dataclasses import dataclass -from openfeature.event import ProviderEventDetails from openfeature.exception import ParseError -from openfeature.provider.provider import AbstractProvider class FlagStore: def __init__( self, - provider: AbstractProvider, + updated_notifier_hook: typing.Callable[[typing.List[str]], None], ): - self.provider = provider + self.updated_notifier_hook = updated_notifier_hook self.flags: typing.Mapping[str, "Flag"] = {} def get_flag(self, key: str) -> typing.Optional["Flag"]: @@ -34,9 +32,7 @@ def update(self, flags_data: dict) -> None: raise ParseError("`flags` key of configuration must be a dictionary") self.flags = {key: Flag.from_dict(key, data) for key, data in flags.items()} - self.provider.emit_provider_configuration_changed( - ProviderEventDetails(flags_changed=list(self.flags.keys())) - ) + self.updated_notifier_hook(list(self.flags.keys())) @dataclass diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/protocol.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/protocol.py new file mode 100644 index 00000000..a6d70fba --- /dev/null +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/protocol.py @@ -0,0 +1,48 @@ +import typing + +from typing_extensions import Protocol + +from openfeature.evaluation_context import EvaluationContext +from openfeature.flag_evaluation import FlagResolutionDetails + + +class AbstractResolver(Protocol): + def initialize(self, evaluation_context: EvaluationContext) -> None: + return + + def shutdown(self) -> None: ... + + def resolve_boolean_details( + self, + key: str, + default_value: bool, + evaluation_context: typing.Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[bool]: ... + + def resolve_string_details( + self, + key: str, + default_value: str, + evaluation_context: typing.Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[str]: ... + + def resolve_float_details( + self, + key: str, + default_value: float, + evaluation_context: typing.Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[float]: ... + + def resolve_integer_details( + self, + key: str, + default_value: int, + evaluation_context: typing.Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[int]: ... + + def resolve_object_details( + self, + key: str, + default_value: typing.Union[dict, list], + evaluation_context: typing.Optional[EvaluationContext] = None, + ) -> FlagResolutionDetails[typing.Union[dict, list]]: ...