Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions open_feature/flag_evaluation/flag_evaluation_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typing
from dataclasses import dataclass, field

from open_feature.hooks.hook import Hook


@dataclass
class FlagEvaluationOptions:
hooks: typing.List[Hook] = field(default_factory=list)
hook_hints: dict = field(default_factory=dict)
2 changes: 2 additions & 0 deletions open_feature/flag_evaluation/flag_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ class FlagType(Enum):
STRING = 2
NUMBER = 3
OBJECT = 4
FLOAT = 5
INTEGER = 6
104 changes: 97 additions & 7 deletions open_feature/open_feature_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
from numbers import Number

from open_feature.evaluation_context.evaluation_context import EvaluationContext
from open_feature.exception.exceptions import GeneralError, OpenFeatureError
from open_feature.exception.exceptions import (
GeneralError,
OpenFeatureError,
TypeMismatchError,
)
from open_feature.flag_evaluation.error_code import ErrorCode
from open_feature.flag_evaluation.flag_evaluation_details import FlagEvaluationDetails
from open_feature.flag_evaluation.flag_evaluation_options import FlagEvaluationOptions
from open_feature.flag_evaluation.flag_type import FlagType
from open_feature.flag_evaluation.reason import Reason
from open_feature.hooks.hook import Hook
Expand All @@ -20,6 +25,8 @@
from open_feature.provider.no_op_provider import NoOpProvider
from open_feature.provider.provider import AbstractProvider

NUMERIC_TYPES = [FlagType.FLOAT, FlagType.INTEGER]


class OpenFeatureClient:
def __init__(
Expand Down Expand Up @@ -129,6 +136,64 @@ def get_number_details(
flag_evaluation_options,
)

def get_integer_value(
self,
flag_key: str,
default_value: int,
evaluation_context: EvaluationContext = None,
flag_evaluation_options: typing.Any = None,
) -> int:
return self.get_integer_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value

def get_integer_details(
self,
flag_key: str,
default_value: int,
evaluation_context: EvaluationContext = None,
flag_evaluation_options: typing.Any = None,
) -> FlagEvaluationDetails:
return self.evaluate_flag_details(
FlagType.INTEGER,
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
)

def get_float_value(
self,
flag_key: str,
default_value: float,
evaluation_context: EvaluationContext = None,
flag_evaluation_options: typing.Any = None,
) -> float:
return self.get_float_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value

def get_float_details(
self,
flag_key: str,
default_value: float,
evaluation_context: EvaluationContext = None,
flag_evaluation_options: typing.Any = None,
) -> FlagEvaluationDetails:
return self.evaluate_flag_details(
FlagType.FLOAT,
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
)

def get_object_value(
self,
flag_key: str,
Expand Down Expand Up @@ -165,7 +230,7 @@ def evaluate_flag_details(
flag_key: str,
default_value: typing.Any,
evaluation_context: EvaluationContext = None,
flag_evaluation_options: typing.Any = None,
flag_evaluation_options: FlagEvaluationOptions = None,
) -> FlagEvaluationDetails:
"""
Evaluate the flag requested by the user from the clients provider.
Expand All @@ -182,6 +247,9 @@ def evaluate_flag_details(
if evaluation_context is None:
evaluation_context = EvaluationContext()

if flag_evaluation_options is None:
flag_evaluation_options = FlagEvaluationOptions()

hook_context = HookContext(
flag_key=flag_key,
flag_type=flag_type,
Expand All @@ -190,18 +258,21 @@ def evaluate_flag_details(
client_metadata=None,
provider_metadata=None,
)
merged_hooks = self.hooks
merged_hooks = (
self.provider.get_provider_hooks()
+ flag_evaluation_options.hooks
+ self.hooks
)
Comment on lines +261 to +265
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will eventually want to implement the API-level hooks.


try:
# https://github.com/open-feature/spec/blob/main/specification/sections/03-evaluation-context.md
# Any resulting evaluation context from a before hook will overwrite
# duplicate fields defined globally, on the client, or in the invocation.
# Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
invocation_context = before_hooks(
flag_type, hook_context, merged_hooks, None
)
invocation_context.merge(ctx2=evaluation_context)

# merge of: API.context, client.context, invocation.context
merged_context = (
api_evaluation_context().merge(self.context).merge(invocation_context)
)
Expand All @@ -213,7 +284,7 @@ def evaluate_flag_details(
merged_context,
)

after_hooks(type, hook_context, flag_evaluation, merged_hooks, None)
after_hooks(flag_type, hook_context, flag_evaluation, merged_hooks, None)

return flag_evaluation

Expand Down Expand Up @@ -272,11 +343,30 @@ def _create_provider_evaluation(
get_details_callable = {
FlagType.BOOLEAN: self.provider.get_boolean_details,
FlagType.NUMBER: self.provider.get_number_details,
FlagType.INTEGER: self.provider.get_number_details,
FlagType.FLOAT: self.provider.get_number_details,
FlagType.OBJECT: self.provider.get_object_details,
FlagType.STRING: self.provider.get_string_details,
}.get(flag_type)

if not get_details_callable:
raise GeneralError(error_message="Unknown flag type")

return get_details_callable(*args)
value = get_details_callable(*args)

if flag_type in NUMERIC_TYPES:
value.value = self._convert_numeric_types(flag_type, value.value)

return value

@staticmethod
def _convert_numeric_types(flag_type: FlagType, current_value: Number):
converter = {
FlagType.FLOAT: float,
FlagType.INTEGER: int,
}.get(flag_type)

try:
return converter(current_value)
except ValueError:
raise TypeMismatchError()
5 changes: 5 additions & 0 deletions open_feature/provider/no_op_provider.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import typing
from numbers import Number

from open_feature.evaluation_context.evaluation_context import EvaluationContext
from open_feature.flag_evaluation.flag_evaluation_details import FlagEvaluationDetails
from open_feature.flag_evaluation.reason import Reason
from open_feature.hooks.hook import Hook
from open_feature.provider.metadata import Metadata
from open_feature.provider.no_op_metadata import NoOpMetadata
from open_feature.provider.provider import AbstractProvider
Expand All @@ -14,6 +16,9 @@ class NoOpProvider(AbstractProvider):
def get_metadata(self) -> Metadata:
return NoOpMetadata()

def get_provider_hooks(self) -> typing.List[Hook]:
return []

def get_boolean_details(
self,
flag_key: str,
Expand Down
6 changes: 6 additions & 0 deletions open_feature/provider/provider.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import typing
from abc import abstractmethod
from numbers import Number

from open_feature.evaluation_context.evaluation_context import EvaluationContext
from open_feature.hooks.hook import Hook
from open_feature.provider.metadata import Metadata


Expand All @@ -10,6 +12,10 @@ class AbstractProvider:
def get_metadata(self) -> Metadata:
pass

@abstractmethod
def get_provider_hooks(self) -> typing.List[Hook]:
return []

@abstractmethod
def get_boolean_details(
self,
Expand Down
4 changes: 4 additions & 0 deletions tests/test_open_feature_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
(bool, True, "get_boolean_value"),
(str, "String", "get_string_value"),
(Number, 100, "get_number_value"),
(int, 100, "get_integer_value"),
(float, 10.23, "get_float_value"),
(
dict,
{
Expand Down Expand Up @@ -46,6 +48,8 @@ def test_should_get_flag_value_based_on_method_type(
(bool, True, "get_boolean_details"),
(str, "String", "get_string_details"),
(Number, 100, "get_number_details"),
(int, 100, "get_integer_details"),
(float, 10.23, "get_float_details"),
(
dict,
{
Expand Down