Merge remote-tracking branch 'origin/main' into fix-imports
This commit is contained in:
commit
f087c4096b
|
|
@ -80,10 +80,10 @@ jobs:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3
|
uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3
|
||||||
with:
|
with:
|
||||||
languages: python
|
languages: python
|
||||||
config-file: ./.github/codeql-config.yml
|
config-file: ./.github/codeql-config.yml
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3
|
uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
default_stages: [pre-commit]
|
default_stages: [pre-commit]
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.3
|
rev: v0.11.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix]
|
args: [--fix]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
* @open-feature/sdk-python-maintainers @open-feature/maintainers
|
||||||
|
|
@ -444,12 +444,12 @@ class OpenFeatureClient:
|
||||||
|
|
||||||
def _assert_provider_status(
|
def _assert_provider_status(
|
||||||
self,
|
self,
|
||||||
) -> None:
|
) -> typing.Optional[OpenFeatureError]:
|
||||||
status = self.get_provider_status()
|
status = self.get_provider_status()
|
||||||
if status == ProviderStatus.NOT_READY:
|
if status == ProviderStatus.NOT_READY:
|
||||||
raise ProviderNotReadyError()
|
return ProviderNotReadyError()
|
||||||
if status == ProviderStatus.FATAL:
|
if status == ProviderStatus.FATAL:
|
||||||
raise ProviderFatalError()
|
return ProviderFatalError()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _before_hooks_and_merge_context(
|
def _before_hooks_and_merge_context(
|
||||||
|
|
@ -509,7 +509,22 @@ class OpenFeatureClient:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._assert_provider_status()
|
if provider_err := self._assert_provider_status():
|
||||||
|
error_hooks(
|
||||||
|
flag_type,
|
||||||
|
hook_context,
|
||||||
|
provider_err,
|
||||||
|
reversed_merged_hooks,
|
||||||
|
hook_hints,
|
||||||
|
)
|
||||||
|
flag_evaluation = FlagEvaluationDetails(
|
||||||
|
flag_key=flag_key,
|
||||||
|
value=default_value,
|
||||||
|
reason=Reason.ERROR,
|
||||||
|
error_code=provider_err.error_code,
|
||||||
|
error_message=provider_err.error_message,
|
||||||
|
)
|
||||||
|
return flag_evaluation
|
||||||
|
|
||||||
merged_context = self._before_hooks_and_merge_context(
|
merged_context = self._before_hooks_and_merge_context(
|
||||||
flag_type,
|
flag_type,
|
||||||
|
|
@ -526,6 +541,11 @@ class OpenFeatureClient:
|
||||||
default_value,
|
default_value,
|
||||||
merged_context,
|
merged_context,
|
||||||
)
|
)
|
||||||
|
if err := flag_evaluation.get_exception():
|
||||||
|
error_hooks(
|
||||||
|
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
|
||||||
|
)
|
||||||
|
return flag_evaluation
|
||||||
|
|
||||||
after_hooks(
|
after_hooks(
|
||||||
flag_type,
|
flag_type,
|
||||||
|
|
@ -605,7 +625,22 @@ class OpenFeatureClient:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._assert_provider_status()
|
if provider_err := self._assert_provider_status():
|
||||||
|
error_hooks(
|
||||||
|
flag_type,
|
||||||
|
hook_context,
|
||||||
|
provider_err,
|
||||||
|
reversed_merged_hooks,
|
||||||
|
hook_hints,
|
||||||
|
)
|
||||||
|
flag_evaluation = FlagEvaluationDetails(
|
||||||
|
flag_key=flag_key,
|
||||||
|
value=default_value,
|
||||||
|
reason=Reason.ERROR,
|
||||||
|
error_code=provider_err.error_code,
|
||||||
|
error_message=provider_err.error_message,
|
||||||
|
)
|
||||||
|
return flag_evaluation
|
||||||
|
|
||||||
merged_context = self._before_hooks_and_merge_context(
|
merged_context = self._before_hooks_and_merge_context(
|
||||||
flag_type,
|
flag_type,
|
||||||
|
|
@ -622,6 +657,12 @@ class OpenFeatureClient:
|
||||||
default_value,
|
default_value,
|
||||||
merged_context,
|
merged_context,
|
||||||
)
|
)
|
||||||
|
if err := flag_evaluation.get_exception():
|
||||||
|
error_hooks(
|
||||||
|
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
|
||||||
|
)
|
||||||
|
flag_evaluation.value = default_value
|
||||||
|
return flag_evaluation
|
||||||
|
|
||||||
after_hooks(
|
after_hooks(
|
||||||
flag_type,
|
flag_type,
|
||||||
|
|
@ -691,28 +732,34 @@ class OpenFeatureClient:
|
||||||
}
|
}
|
||||||
get_details_callable = get_details_callables_async.get(flag_type)
|
get_details_callable = get_details_callables_async.get(flag_type)
|
||||||
if not get_details_callable:
|
if not get_details_callable:
|
||||||
raise GeneralError(error_message="Unknown flag type")
|
return FlagEvaluationDetails(
|
||||||
|
flag_key=flag_key,
|
||||||
|
value=default_value,
|
||||||
|
reason=Reason.ERROR,
|
||||||
|
error_code=ErrorCode.GENERAL,
|
||||||
|
error_message="Unknown flag type",
|
||||||
|
)
|
||||||
|
|
||||||
resolution = await get_details_callable(
|
resolution = await get_details_callable(
|
||||||
flag_key=flag_key,
|
flag_key=flag_key,
|
||||||
default_value=default_value,
|
default_value=default_value,
|
||||||
evaluation_context=evaluation_context,
|
evaluation_context=evaluation_context,
|
||||||
)
|
)
|
||||||
resolution.raise_for_error()
|
if resolution.error_code:
|
||||||
|
return resolution.to_flag_evaluation_details(flag_key)
|
||||||
|
|
||||||
# we need to check the get_args to be compatible with union types.
|
# we need to check the get_args to be compatible with union types.
|
||||||
_typecheck_flag_value(resolution.value, flag_type)
|
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
|
||||||
|
|
||||||
return FlagEvaluationDetails(
|
return FlagEvaluationDetails(
|
||||||
flag_key=flag_key,
|
flag_key=flag_key,
|
||||||
value=resolution.value,
|
value=resolution.value,
|
||||||
variant=resolution.variant,
|
reason=Reason.ERROR,
|
||||||
flag_metadata=resolution.flag_metadata or {},
|
error_code=err.error_code,
|
||||||
reason=resolution.reason,
|
error_message=err.error_message,
|
||||||
error_code=resolution.error_code,
|
|
||||||
error_message=resolution.error_message,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return resolution.to_flag_evaluation_details(flag_key)
|
||||||
|
|
||||||
def _create_provider_evaluation(
|
def _create_provider_evaluation(
|
||||||
self,
|
self,
|
||||||
provider: FeatureProvider,
|
provider: FeatureProvider,
|
||||||
|
|
@ -741,28 +788,34 @@ class OpenFeatureClient:
|
||||||
|
|
||||||
get_details_callable = get_details_callables.get(flag_type)
|
get_details_callable = get_details_callables.get(flag_type)
|
||||||
if not get_details_callable:
|
if not get_details_callable:
|
||||||
raise GeneralError(error_message="Unknown flag type")
|
return FlagEvaluationDetails(
|
||||||
|
flag_key=flag_key,
|
||||||
|
value=default_value,
|
||||||
|
reason=Reason.ERROR,
|
||||||
|
error_code=ErrorCode.GENERAL,
|
||||||
|
error_message="Unknown flag type",
|
||||||
|
)
|
||||||
|
|
||||||
resolution = get_details_callable(
|
resolution = get_details_callable(
|
||||||
flag_key=flag_key,
|
flag_key=flag_key,
|
||||||
default_value=default_value,
|
default_value=default_value,
|
||||||
evaluation_context=evaluation_context,
|
evaluation_context=evaluation_context,
|
||||||
)
|
)
|
||||||
resolution.raise_for_error()
|
if resolution.error_code:
|
||||||
|
return resolution.to_flag_evaluation_details(flag_key)
|
||||||
|
|
||||||
# we need to check the get_args to be compatible with union types.
|
# we need to check the get_args to be compatible with union types.
|
||||||
_typecheck_flag_value(resolution.value, flag_type)
|
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
|
||||||
|
|
||||||
return FlagEvaluationDetails(
|
return FlagEvaluationDetails(
|
||||||
flag_key=flag_key,
|
flag_key=flag_key,
|
||||||
value=resolution.value,
|
value=resolution.value,
|
||||||
variant=resolution.variant,
|
reason=Reason.ERROR,
|
||||||
flag_metadata=resolution.flag_metadata or {},
|
error_code=err.error_code,
|
||||||
reason=resolution.reason,
|
error_message=err.error_message,
|
||||||
error_code=resolution.error_code,
|
|
||||||
error_message=resolution.error_message,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return resolution.to_flag_evaluation_details(flag_key)
|
||||||
|
|
||||||
def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
|
def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
|
||||||
_event_support.add_client_handler(self, event, handler)
|
_event_support.add_client_handler(self, event, handler)
|
||||||
|
|
||||||
|
|
@ -770,7 +823,9 @@ class OpenFeatureClient:
|
||||||
_event_support.remove_client_handler(self, event, handler)
|
_event_support.remove_client_handler(self, event, handler)
|
||||||
|
|
||||||
|
|
||||||
def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
|
def _typecheck_flag_value(
|
||||||
|
value: typing.Any, flag_type: FlagType
|
||||||
|
) -> typing.Optional[OpenFeatureError]:
|
||||||
type_map: TypeMap = {
|
type_map: TypeMap = {
|
||||||
FlagType.BOOLEAN: bool,
|
FlagType.BOOLEAN: bool,
|
||||||
FlagType.STRING: str,
|
FlagType.STRING: str,
|
||||||
|
|
@ -780,6 +835,7 @@ def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
|
||||||
}
|
}
|
||||||
_type = type_map.get(flag_type)
|
_type = type_map.get(flag_type)
|
||||||
if not _type:
|
if not _type:
|
||||||
raise GeneralError(error_message="Unknown flag type")
|
return GeneralError(error_message="Unknown flag type")
|
||||||
if not isinstance(value, _type):
|
if not isinstance(value, _type):
|
||||||
raise TypeMismatchError(f"Expected type {_type} but got {type(value)}")
|
return TypeMismatchError(f"Expected type {_type} but got {type(value)}")
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import typing
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from openfeature._backports.strenum import StrEnum
|
from openfeature._backports.strenum import StrEnum
|
||||||
from openfeature.exception import ErrorCode
|
from openfeature.exception import ErrorCode, OpenFeatureError
|
||||||
|
|
||||||
if typing.TYPE_CHECKING: # pragma: no cover
|
if typing.TYPE_CHECKING: # pragma: no cover
|
||||||
# resolves a circular dependency in type annotations
|
# resolves a circular dependency in type annotations
|
||||||
|
|
@ -56,6 +56,11 @@ class FlagEvaluationDetails(typing.Generic[T_co]):
|
||||||
error_code: typing.Optional[ErrorCode] = None
|
error_code: typing.Optional[ErrorCode] = None
|
||||||
error_message: typing.Optional[str] = None
|
error_message: typing.Optional[str] = None
|
||||||
|
|
||||||
|
def get_exception(self) -> typing.Optional[OpenFeatureError]:
|
||||||
|
if self.error_code:
|
||||||
|
return ErrorCode.to_exception(self.error_code, self.error_message or "")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlagEvaluationOptions:
|
class FlagEvaluationOptions:
|
||||||
|
|
@ -79,3 +84,14 @@ class FlagResolutionDetails(typing.Generic[U_co]):
|
||||||
if self.error_code:
|
if self.error_code:
|
||||||
raise ErrorCode.to_exception(self.error_code, self.error_message or "")
|
raise ErrorCode.to_exception(self.error_code, self.error_message or "")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def to_flag_evaluation_details(self, flag_key: str) -> FlagEvaluationDetails[U_co]:
|
||||||
|
return FlagEvaluationDetails(
|
||||||
|
flag_key=flag_key,
|
||||||
|
value=self.value,
|
||||||
|
variant=self.variant,
|
||||||
|
flag_metadata=self.flag_metadata,
|
||||||
|
reason=self.reason,
|
||||||
|
error_code=self.error_code,
|
||||||
|
error_message=self.error_message,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from dataclasses import dataclass, field
|
||||||
|
|
||||||
from openfeature._backports.strenum import StrEnum
|
from openfeature._backports.strenum import StrEnum
|
||||||
from openfeature.evaluation_context import EvaluationContext
|
from openfeature.evaluation_context import EvaluationContext
|
||||||
from openfeature.exception import FlagNotFoundError
|
from openfeature.exception import ErrorCode
|
||||||
from openfeature.flag_evaluation import FlagMetadata, FlagResolutionDetails, Reason
|
from openfeature.flag_evaluation import FlagMetadata, FlagResolutionDetails, Reason
|
||||||
from openfeature.hook import Hook
|
from openfeature.hook import Hook
|
||||||
from openfeature.provider import AbstractProvider, Metadata
|
from openfeature.provider import AbstractProvider, Metadata
|
||||||
|
|
@ -74,7 +74,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: bool,
|
default_value: bool,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[bool]:
|
) -> FlagResolutionDetails[bool]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
async def resolve_boolean_details_async(
|
async def resolve_boolean_details_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -82,7 +82,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: bool,
|
default_value: bool,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[bool]:
|
) -> FlagResolutionDetails[bool]:
|
||||||
return await self._resolve_async(flag_key, evaluation_context)
|
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
def resolve_string_details(
|
def resolve_string_details(
|
||||||
self,
|
self,
|
||||||
|
|
@ -90,7 +90,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: str,
|
default_value: str,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[str]:
|
) -> FlagResolutionDetails[str]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
async def resolve_string_details_async(
|
async def resolve_string_details_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -98,7 +98,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: str,
|
default_value: str,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[str]:
|
) -> FlagResolutionDetails[str]:
|
||||||
return await self._resolve_async(flag_key, evaluation_context)
|
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
def resolve_integer_details(
|
def resolve_integer_details(
|
||||||
self,
|
self,
|
||||||
|
|
@ -106,7 +106,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: int,
|
default_value: int,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[int]:
|
) -> FlagResolutionDetails[int]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
async def resolve_integer_details_async(
|
async def resolve_integer_details_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -114,7 +114,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: int,
|
default_value: int,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[int]:
|
) -> FlagResolutionDetails[int]:
|
||||||
return await self._resolve_async(flag_key, evaluation_context)
|
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
def resolve_float_details(
|
def resolve_float_details(
|
||||||
self,
|
self,
|
||||||
|
|
@ -122,7 +122,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: float,
|
default_value: float,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[float]:
|
) -> FlagResolutionDetails[float]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
async def resolve_float_details_async(
|
async def resolve_float_details_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -130,7 +130,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: float,
|
default_value: float,
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[float]:
|
) -> FlagResolutionDetails[float]:
|
||||||
return await self._resolve_async(flag_key, evaluation_context)
|
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
def resolve_object_details(
|
def resolve_object_details(
|
||||||
self,
|
self,
|
||||||
|
|
@ -138,7 +138,7 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: typing.Union[dict, list],
|
default_value: typing.Union[dict, list],
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
async def resolve_object_details_async(
|
async def resolve_object_details_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -146,21 +146,28 @@ class InMemoryProvider(AbstractProvider):
|
||||||
default_value: typing.Union[dict, list],
|
default_value: typing.Union[dict, list],
|
||||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||||
return await self._resolve_async(flag_key, evaluation_context)
|
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||||
|
|
||||||
def _resolve(
|
def _resolve(
|
||||||
self,
|
self,
|
||||||
flag_key: str,
|
flag_key: str,
|
||||||
|
default_value: V,
|
||||||
evaluation_context: typing.Optional[EvaluationContext],
|
evaluation_context: typing.Optional[EvaluationContext],
|
||||||
) -> FlagResolutionDetails[V]:
|
) -> FlagResolutionDetails[V]:
|
||||||
flag = self._flags.get(flag_key)
|
flag = self._flags.get(flag_key)
|
||||||
if flag is None:
|
if flag is None:
|
||||||
raise FlagNotFoundError(f"Flag '{flag_key}' not found")
|
return FlagResolutionDetails(
|
||||||
|
value=default_value,
|
||||||
|
reason=Reason.ERROR,
|
||||||
|
error_code=ErrorCode.FLAG_NOT_FOUND,
|
||||||
|
error_message=f"Flag '{flag_key}' not found",
|
||||||
|
)
|
||||||
return flag.resolve(evaluation_context)
|
return flag.resolve(evaluation_context)
|
||||||
|
|
||||||
async def _resolve_async(
|
async def _resolve_async(
|
||||||
self,
|
self,
|
||||||
flag_key: str,
|
flag_key: str,
|
||||||
|
default_value: V,
|
||||||
evaluation_context: typing.Optional[EvaluationContext],
|
evaluation_context: typing.Optional[EvaluationContext],
|
||||||
) -> FlagResolutionDetails[V]:
|
) -> FlagResolutionDetails[V]:
|
||||||
return self._resolve(flag_key, evaluation_context)
|
return self._resolve(flag_key, default_value, evaluation_context)
|
||||||
|
|
|
||||||
2
spec
2
spec
|
|
@ -1 +1 @@
|
||||||
Subproject commit 27e4461b452429ec64bcb89af0301f7664cb702a
|
Subproject commit 0cd553d85f03b0b7ab983a988ce1720a3190bc88
|
||||||
|
|
@ -2,7 +2,7 @@ from numbers import Number
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from openfeature.exception import FlagNotFoundError
|
from openfeature.exception import ErrorCode
|
||||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
||||||
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
||||||
|
|
||||||
|
|
@ -22,11 +22,18 @@ async def test_should_handle_unknown_flags_correctly():
|
||||||
# Given
|
# Given
|
||||||
provider = InMemoryProvider({})
|
provider = InMemoryProvider({})
|
||||||
# When
|
# When
|
||||||
with pytest.raises(FlagNotFoundError):
|
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=True)
|
||||||
provider.resolve_boolean_details(flag_key="Key", default_value=True)
|
flag_async = await provider.resolve_boolean_details_async(
|
||||||
with pytest.raises(FlagNotFoundError):
|
flag_key="Key", default_value=True
|
||||||
await provider.resolve_integer_details_async(flag_key="Key", default_value=1)
|
)
|
||||||
# Then
|
# Then
|
||||||
|
assert flag_sync == flag_async
|
||||||
|
for flag in [flag_sync, flag_async]:
|
||||||
|
assert flag is not None
|
||||||
|
assert flag.value is True
|
||||||
|
assert flag.reason == Reason.ERROR
|
||||||
|
assert flag.error_code == ErrorCode.FLAG_NOT_FOUND
|
||||||
|
assert flag.error_message == "Flag 'Key' not found"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import pytest
|
||||||
|
|
||||||
from openfeature import api
|
from openfeature import api
|
||||||
from openfeature.api import add_hooks, clear_hooks, get_client, set_provider
|
from openfeature.api import add_hooks, clear_hooks, get_client, set_provider
|
||||||
from openfeature.client import GeneralError, OpenFeatureClient, _typecheck_flag_value
|
from openfeature.client import OpenFeatureClient, _typecheck_flag_value
|
||||||
from openfeature.evaluation_context import EvaluationContext
|
from openfeature.evaluation_context import EvaluationContext
|
||||||
from openfeature.event import EventDetails, ProviderEvent, ProviderEventDetails
|
from openfeature.event import EventDetails, ProviderEvent, ProviderEventDetails
|
||||||
from openfeature.exception import ErrorCode, OpenFeatureError
|
from openfeature.exception import ErrorCode, OpenFeatureError
|
||||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
from openfeature.flag_evaluation import FlagResolutionDetails, FlagType, Reason
|
||||||
from openfeature.hook import Hook
|
from openfeature.hook import Hook
|
||||||
from openfeature.provider import FeatureProvider, ProviderStatus
|
from openfeature.provider import FeatureProvider, ProviderStatus
|
||||||
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
||||||
|
|
@ -364,15 +364,27 @@ async def test_client_type_mismatch_exceptions():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_client_general_exception():
|
async def test_typecheck_flag_value_general_error():
|
||||||
# Given
|
# Given
|
||||||
flag_value = "A"
|
flag_value = "A"
|
||||||
flag_type = None
|
flag_type = None
|
||||||
# When
|
# When
|
||||||
with pytest.raises(GeneralError) as e:
|
err = _typecheck_flag_value(value=flag_value, flag_type=flag_type)
|
||||||
flag_type = _typecheck_flag_value(flag_value, flag_type)
|
|
||||||
# Then
|
# Then
|
||||||
assert e.value.error_message == "Unknown flag type"
|
assert err.error_code == ErrorCode.GENERAL
|
||||||
|
assert err.error_message == "Unknown flag type"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_typecheck_flag_value_type_mismatch_error():
|
||||||
|
# Given
|
||||||
|
flag_value = "A"
|
||||||
|
flag_type = FlagType.BOOLEAN
|
||||||
|
# When
|
||||||
|
err = _typecheck_flag_value(value=flag_value, flag_type=flag_type)
|
||||||
|
# Then
|
||||||
|
assert err.error_code == ErrorCode.TYPE_MISMATCH
|
||||||
|
assert err.error_message == "Expected type <class 'bool'> but got <class 'str'>"
|
||||||
|
|
||||||
|
|
||||||
def test_provider_events():
|
def test_provider_events():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue