diff --git a/ld_openfeature/impl/context_converter.py b/ld_openfeature/impl/context_converter.py index 5fca4a4..7c4c8ac 100644 --- a/ld_openfeature/impl/context_converter.py +++ b/ld_openfeature/impl/context_converter.py @@ -1,8 +1,8 @@ from logging import getLogger -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Mapping, Optional from ldclient.context import Context, ContextBuilder, ContextMultiBuilder -from openfeature.evaluation_context import EvaluationContext +from openfeature.evaluation_context import EvaluationContext, EvaluationContextAttribute logger = getLogger("launchdarkly-openfeature-server") @@ -78,7 +78,7 @@ def __build_multi_context(self, context: EvaluationContext) -> Context: return builder.build() - def __build_single_context(self, attributes: Dict, kind: str, key: str) -> Context: + def __build_single_context(self, attributes: Mapping[str, EvaluationContextAttribute], kind: str, key: str) -> Context: builder = ContextBuilder(key) builder.kind(kind) diff --git a/ld_openfeature/provider.py b/ld_openfeature/provider.py index 28f5e4f..5865b98 100644 --- a/ld_openfeature/provider.py +++ b/ld_openfeature/provider.py @@ -1,12 +1,12 @@ import threading -from typing import Any, List, Optional, Union +from typing import Any, List, Mapping, Optional, Sequence, Union from ldclient.evaluation import EvaluationDetail from ldclient import LDClient, Config from ldclient.interfaces import DataSourceStatus, FlagChange, DataSourceState from openfeature.evaluation_context import EvaluationContext from openfeature.exception import ErrorCode, ProviderFatalError -from openfeature.flag_evaluation import FlagResolutionDetails, FlagType, Reason +from openfeature.flag_evaluation import FlagResolutionDetails, FlagType, FlagValueType, Reason from openfeature.hook import Hook from openfeature.provider.metadata import Metadata from openfeature.provider import AbstractProvider @@ -133,7 +133,9 @@ def resolve_float_details( def resolve_object_details( self, flag_key: str, - default_value: Union[dict, list], + default_value: Union[ + Sequence[FlagValueType], Mapping[str, FlagValueType] + ], evaluation_context: Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[Union[dict, list]]: """Resolves the flag value for the provided flag key as a list or dictionary""" @@ -154,15 +156,15 @@ def __resolve_value(self, flag_type: FlagType, flag_key: str, default_value: Any resolved_value = self.__validate_and_cast_value(flag_type, result.value) if resolved_value is None: return self.__mismatched_type_details(default_value) - + resolved_detail = EvaluationDetail( value=resolved_value, variation_index=result.variation_index, reason=result.reason, ) - + return self.__details_converter.to_resolution_details(resolved_detail) - + def __validate_and_cast_value(self, flag_type: FlagType, value: Any): """Serializes the raw flag value to the expected type based on flag_type.""" if flag_type == FlagType.BOOLEAN and isinstance(value, bool): @@ -175,7 +177,7 @@ def __validate_and_cast_value(self, flag_type: FlagType, value: Any): return float(value) elif flag_type == FlagType.OBJECT and isinstance(value, (dict, list)): return value - return None + return None @staticmethod def __mismatched_type_details(default_value: Any) -> FlagResolutionDetails: diff --git a/pyproject.toml b/pyproject.toml index 7757262..34bd7d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ packages = [ [tool.poetry.dependencies] python = "^3.9" -openfeature-sdk = ">=0.7.0,<1" +openfeature-sdk = ">=0.8.0,<1" launchdarkly-server-sdk = "<10" diff --git a/tests/impl/test_context_converter.py b/tests/impl/test_context_converter.py index fbdd679..cd0afff 100644 --- a/tests/impl/test_context_converter.py +++ b/tests/impl/test_context_converter.py @@ -1,5 +1,6 @@ +from typing import Mapping import pytest -from openfeature.evaluation_context import EvaluationContext +from openfeature.evaluation_context import EvaluationContext, EvaluationContextAttribute from ld_openfeature.impl.context_converter import EvaluationContextConverter @@ -49,7 +50,7 @@ def test_invalid_private_attribute_types_are_ignored(context_converter: Evaluati def test_create_multi_context_with_invalid_targeting_key(context_converter: EvaluationContextConverter): - attributes = { + attributes: Mapping[str, EvaluationContextAttribute] = { 'kind': 'multi', 'user': {'targetingKey': False, 'key': 'user-key', 'name': 'User name'}, 'org': {'key': 'org-key', 'name': 'Org name'}, @@ -142,7 +143,7 @@ def test_private_attributes_are_processed_correctly(context_converter: Evaluatio def test_can_create_multi_kind_context(context_converter: EvaluationContextConverter): - attributes = { + attributes: Mapping[str, EvaluationContextAttribute] = { 'kind': 'multi', 'user': {'key': 'user-key', 'name': 'User name'}, 'org': {'key': 'org-key', 'name': 'Org name'}, @@ -167,7 +168,7 @@ def test_can_create_multi_kind_context(context_converter: EvaluationContextConve def test_can_multi_kind_ignores_kind_attribute(context_converter: EvaluationContextConverter): - attributes = { + attributes: Mapping[str, EvaluationContextAttribute] = { 'kind': 'multi', 'user': {'key': 'user-key', 'kind': 'device', 'name': 'User name'}, 'org': {'key': 'org-key', 'name': 'Org name'}, @@ -192,7 +193,7 @@ def test_can_multi_kind_ignores_kind_attribute(context_converter: EvaluationCont def test_multi_context_discards_invalid_single_kind(context_converter: EvaluationContextConverter): - attributes = { + attributes: Mapping[str, EvaluationContextAttribute] = { 'kind': 'multi', 'user': False, 'org': {'key': 'org-key', 'name': 'Org name'}, @@ -208,7 +209,7 @@ def test_multi_context_discards_invalid_single_kind(context_converter: Evaluatio def test_handles_invalid_nested_contexts(context_converter: EvaluationContextConverter): - attributes = { + attributes: Mapping[str, EvaluationContextAttribute] = { 'kind': 'multi', 'user': 'invalid format', 'org': False