Skip to content

Commit

Permalink
fix: AbstractProvider dependency everywhere
Browse files Browse the repository at this point in the history
Signed-off-by: Cole Bailey <cole.bailey@deliveryhero.com>
  • Loading branch information
colebaileygit committed Jun 20, 2024
1 parent 45e3739 commit e9cd2ed
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,7 +76,12 @@ def setup_resolver(self) -> AbstractResolver:
if self.config.resolver_type == ResolverType.GRPC:
return GrpcResolver(self.config)
elif self.config.resolver_type == ResolverType.IN_PROCESS:
return InProcessResolver(self.config, self)
return InProcessResolver(
self.config,
self.emit_provider_ready,
self.emit_provider_error,
self.emit_provider_configuration_changed,
)
else:
raise ValueError(
f"`resolver_type` parameter invalid: {self.config.resolver_type}"
Expand All @@ -92,6 +98,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(

Check warning on line 102 in providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L102 was not covered by tests
ProviderEventDetails(flags_changed=flag_keys)
)

def resolve_boolean_details(
self,
key: str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from json_logic import builtins, jsonLogic # type: ignore[import-untyped]

from openfeature.evaluation_context import EvaluationContext
from openfeature.event import ProviderEventDetails
from openfeature.exception import FlagNotFoundError, ParseError
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
from openfeature.provider.provider import AbstractProvider

from ..config import Config
from .process.connector import FlagStateConnector
Expand All @@ -27,19 +27,32 @@ class InProcessResolver:
"sem_ver": sem_ver,
}

def __init__(self, config: Config, provider: AbstractProvider):
def __init__(
self,
config: Config,
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
emit_provider_configuration_changed: typing.Callable[
[ProviderEventDetails], None
],
):
self.config = config
self.provider = provider
self.flag_store = FlagStore(provider)
self.flag_store = FlagStore(emit_provider_configuration_changed)
self.connector: FlagStateConnector = (
FileWatcher(
self.config.offline_flag_source_path,
self.provider,
self.flag_store,
emit_provider_ready,
emit_provider_error,
self.config.offline_poll_interval_seconds,
)
if self.config.offline_flag_source_path
else GrpcWatcher(self.config, self.provider, self.flag_store)
else GrpcWatcher(
self.config,
self.flag_store,
emit_provider_ready,
emit_provider_error,
)
)

def initialize(self, evaluation_context: EvaluationContext) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from openfeature.evaluation_context import EvaluationContext
from openfeature.event import ProviderEventDetails
from openfeature.exception import ParseError, ProviderNotReadyError
from openfeature.provider.provider import AbstractProvider

from ..connector import FlagStateConnector
from ..flags import FlagStore
Expand All @@ -22,12 +21,14 @@ class FileWatcher(FlagStateConnector):
def __init__(
self,
file_path: str,
provider: AbstractProvider,
flag_store: FlagStore,
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
poll_interval_seconds: float = 1.0,
):
self.file_path = file_path
self.provider = provider
self.emit_provider_ready = emit_provider_ready
self.emit_provider_error = emit_provider_error
self.poll_interval_seconds = poll_interval_seconds

self.last_modified = 0.0
Expand Down Expand Up @@ -83,7 +84,7 @@ def _load_data(self, modified_time: typing.Optional[float] = None) -> None:
self.flag_store.update(data)

if self.should_emit_ready_on_success:
self.provider.emit_provider_ready(
self.emit_provider_ready(
ProviderEventDetails(
message="Reloading file contents recovered from error state"
)
Expand All @@ -95,4 +96,4 @@ def _load_data(self, modified_time: typing.Optional[float] = None) -> None:
def handle_error(self, error_message: str) -> None:
logger.exception(error_message)
self.should_emit_ready_on_success = True
self.provider.emit_provider_error(ProviderEventDetails(message=error_message))
self.emit_provider_error(ProviderEventDetails(message=error_message))
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import logging
import threading
import time
import typing

import grpc

from openfeature.evaluation_context import EvaluationContext
from openfeature.event import ProviderEventDetails
from openfeature.exception import ErrorCode, ParseError, ProviderNotReadyError
from openfeature.provider.provider import AbstractProvider

from ....config import Config
from ....proto.flagd.sync.v1 import sync_pb2, sync_pb2_grpc
Expand All @@ -22,16 +22,21 @@ class GrpcWatcher(FlagStateConnector):
MAX_BACK_OFF = 120

def __init__(
self, config: Config, provider: AbstractProvider, flag_store: FlagStore
self,
config: Config,
flag_store: FlagStore,
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
):
self.provider = provider
self.flag_store = flag_store
channel_factory = grpc.secure_channel if config.tls else grpc.insecure_channel
self.channel = channel_factory(f"{config.host}:{config.port}")
self.stub = sync_pb2_grpc.FlagSyncServiceStub(self.channel)
self.timeout = config.timeout
self.retry_backoff_seconds = config.retry_backoff_seconds
self.selector = config.selector
self.emit_provider_ready = emit_provider_ready
self.emit_provider_error = emit_provider_error

self.connected = False

Expand Down Expand Up @@ -71,7 +76,7 @@ def sync_flags(self) -> None:
self.flag_store.update(json.loads(flag_str))

if not self.connected:
self.provider.emit_provider_ready(
self.emit_provider_ready(
ProviderEventDetails(
message="gRPC sync connection established"
)
Expand All @@ -94,7 +99,7 @@ def sync_flags(self) -> None:
)

self.connected = False
self.provider.emit_provider_error(
self.emit_provider_error(
ProviderEventDetails(
message=f"gRPC sync disconnected, reconnecting in {retry_delay}s",
error_code=ErrorCode.GENERAL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@

from openfeature.event import ProviderEventDetails
from openfeature.exception import ParseError
from openfeature.provider.provider import AbstractProvider


class FlagStore:
def __init__(
self,
provider: AbstractProvider,
emit_provider_configuration_changed: typing.Callable[
[ProviderEventDetails], None
],
):
self.provider = provider
self.emit_provider_configuration_changed = emit_provider_configuration_changed
self.flags: typing.Mapping[str, "Flag"] = {}

def get_flag(self, key: str) -> typing.Optional["Flag"]:
Expand All @@ -34,7 +35,7 @@ def update(self, flags_data: dict) -> None:
raise ParseError("`flags` key of configuration must be a dictionary")

Check warning on line 35 in providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py

View check run for this annotation

Codecov / codecov/patch

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py#L35

Added line #L35 was not covered by tests
self.flags = {key: Flag.from_dict(key, data) for key, data in flags.items()}

self.provider.emit_provider_configuration_changed(
self.emit_provider_configuration_changed(
ProviderEventDetails(flags_changed=list(self.flags.keys()))
)

Expand Down
11 changes: 7 additions & 4 deletions providers/openfeature-provider-flagd/tests/test_file_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
FileWatcher,
)
from openfeature.contrib.provider.flagd.resolvers.process.flags import Flag, FlagStore
from openfeature.provider.provider import AbstractProvider


def create_client(provider: FlagdProvider):
Expand All @@ -24,9 +23,13 @@ def create_client(provider: FlagdProvider):
],
)
def test_file_load(file_name: str):
provider = Mock(spec=AbstractProvider)
flag_store = FlagStore(provider)
file_watcher = FileWatcher(f"tests/flags/{file_name}", provider, flag_store)
emit_provider_configuration_changed = Mock()
emit_provider_ready = Mock()
emit_provider_error = Mock()
flag_store = FlagStore(emit_provider_configuration_changed)
file_watcher = FileWatcher(
f"tests/flags/{file_name}", flag_store, emit_provider_ready, emit_provider_error
)
file_watcher.initialize(None)

flag = flag_store.get_flag("basic-flag")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ def sync(request):
),
)
def test_invalid_payload(flag_configuration: str):
fake_provider = MagicMock()
flag_store = FlagStore(fake_provider)
watcher = GrpcWatcher(Config(timeout=0.2), fake_provider, flag_store)
emit_provider_configuration_changed = MagicMock()
emit_provider_ready = MagicMock()
emit_provider_error = MagicMock()
flag_store = FlagStore(emit_provider_configuration_changed)
watcher = GrpcWatcher(
Config(timeout=0.2), flag_store, emit_provider_ready, emit_provider_error
)

fake_sync_flags = fake_grpc_service(flag_configuration)
with patch.object(watcher.stub, "SyncFlags", fake_sync_flags), pytest.raises(
Expand Down

0 comments on commit e9cd2ed

Please sign in to comment.