confidence/openfeature_provider.py (169 lines of code) (raw):
import dataclasses
from enum import Enum
from typing import (
Any,
Dict,
List,
Optional,
Type,
Union,
get_args,
)
import openfeature.exception as open_feature_exception
import openfeature.exception
from openfeature.api import EvaluationContext
from openfeature.api import Hook
from openfeature.flag_evaluation import FlagResolutionDetails
from openfeature.provider.metadata import Metadata
from openfeature.provider.provider import AbstractProvider
from typing_extensions import TypeGuard
import confidence.confidence
from confidence.errors import ErrorCode
EU_RESOLVE_API_ENDPOINT = "https://resolver.eu.confidence.dev/v1"
US_RESOLVE_API_ENDPOINT = "https://resolver.us.confidence.dev/v1"
GLOBAL_RESOLVE_API_ENDPOINT = "https://resolver.confidence.dev/v1"
Primitive = Union[str, int, float, bool, None]
FieldType = Union[Primitive, List[Primitive], List["Object"], "Object"]
Object = Dict[str, FieldType]
def is_primitive(field_type: Type[Any]) -> TypeGuard[Type[Primitive]]:
return field_type in get_args(Primitive)
def primitive_matches(value: FieldType, value_type: Type[Primitive]) -> bool:
return (
value_type is None
or (value_type is int and isinstance(value, int))
or (value_type is float and isinstance(value, float))
or (value_type is str and isinstance(value, str))
or (value_type is bool and isinstance(value, bool))
)
class Region(Enum):
def endpoint(self) -> str:
return self.value
EU = EU_RESOLVE_API_ENDPOINT
US = US_RESOLVE_API_ENDPOINT
GLOBAL = GLOBAL_RESOLVE_API_ENDPOINT
@dataclasses.dataclass
class ResolveResult(object):
value: Optional[Object]
variant: Optional[str]
token: str
def _to_openfeature_error_code(
error_code: Optional[ErrorCode],
) -> Optional[open_feature_exception.ErrorCode]:
"""
Convert a confidence error code to an openfeature error code
:param error_code:
:return:
"""
if error_code is None:
return None
if error_code is ErrorCode.FLAG_NOT_FOUND:
return openfeature.exception.ErrorCode.FLAG_NOT_FOUND
if error_code is ErrorCode.TYPE_MISMATCH:
return openfeature.exception.ErrorCode.TYPE_MISMATCH
if error_code is ErrorCode.TARGETING_KEY_MISSING:
return openfeature.exception.ErrorCode.TARGETING_KEY_MISSING
if error_code is ErrorCode.INVALID_CONTEXT:
return openfeature.exception.ErrorCode.INVALID_CONTEXT
if error_code is ErrorCode.GENERAL:
return openfeature.exception.ErrorCode.GENERAL
if error_code is ErrorCode.PARSE_ERROR:
return openfeature.exception.ErrorCode.PARSE_ERROR
if error_code is ErrorCode.NOT_READY:
return openfeature.exception.ErrorCode.PROVIDER_NOT_READY
class ConfidenceOpenFeatureProvider(AbstractProvider):
def __init__(self, confidence_sdk: confidence.confidence.Confidence):
self.confidence_sdk = confidence_sdk
#
# --- Provider API ---
#
def get_metadata(self) -> Metadata:
return Metadata("Confidence")
def get_provider_hooks(self) -> List[Hook]:
return []
def resolve_boolean_details(
self,
flag_key: str,
default_value: bool,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[bool]:
details = self._confidence_with_context(
evaluation_context
).resolve_boolean_details(flag_key, default_value)
return FlagResolutionDetails[bool](
value=details.value,
variant=details.variant,
reason=details.reason,
error_code=_to_openfeature_error_code(details.error_code),
error_message=details.error_message,
flag_metadata=details.flag_metadata,
)
def resolve_float_details(
self,
flag_key: str,
default_value: float,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[float]:
details = self._confidence_with_context(
evaluation_context
).resolve_float_details(flag_key, default_value)
return FlagResolutionDetails[float](
value=details.value,
variant=details.variant,
reason=details.reason,
error_code=_to_openfeature_error_code(details.error_code),
error_message=details.error_message,
flag_metadata=details.flag_metadata,
)
def resolve_integer_details(
self,
flag_key: str,
default_value: int,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[int]:
details = self._confidence_with_context(
evaluation_context
).resolve_integer_details(flag_key, default_value)
return FlagResolutionDetails[int](
value=details.value,
variant=details.variant,
reason=details.reason,
error_code=_to_openfeature_error_code(details.error_code),
error_message=details.error_message,
flag_metadata=details.flag_metadata,
)
def resolve_string_details(
self,
flag_key: str,
default_value: str,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[str]:
details = self._confidence_with_context(
evaluation_context
).resolve_string_details(flag_key, default_value)
return FlagResolutionDetails[str](
value=details.value,
variant=details.variant,
reason=details.reason,
error_code=_to_openfeature_error_code(details.error_code),
error_message=details.error_message,
flag_metadata=details.flag_metadata,
)
def resolve_object_details(
self,
flag_key: str,
default_value: Union[Object, List[Primitive]],
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[Union[Object, List[Primitive]]]:
details = self._confidence_with_context(
evaluation_context
).resolve_object_details(flag_key, default_value)
return FlagResolutionDetails[Union[Object, List[Primitive]]](
value=details.value,
variant=details.variant,
reason=details.reason,
error_code=_to_openfeature_error_code(details.error_code),
error_message=details.error_message,
flag_metadata=details.flag_metadata,
)
def _confidence_with_context(
self, evaluation_context: Optional[EvaluationContext]
) -> confidence.confidence.Confidence:
eval_context: Dict[str, FieldType] = {}
if evaluation_context:
if evaluation_context.targeting_key:
eval_context["targeting_key"] = evaluation_context.targeting_key
# add other fields to eval_context from evaluationContext
for key, value in evaluation_context.attributes.items():
eval_context[key] = value
return self.confidence_sdk.with_context(eval_context)