Skip to content

Commit cf1e516

Browse files
committed
replace exception raising with error flag resolution
Signed-off-by: gruebel <[email protected]>
1 parent a135911 commit cf1e516

File tree

5 files changed

+162
-65
lines changed

5 files changed

+162
-65
lines changed

openfeature/client.py

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -446,12 +446,12 @@ def _establish_hooks_and_provider(
446446

447447
def _assert_provider_status(
448448
self,
449-
) -> None:
449+
) -> typing.Optional[OpenFeatureError]:
450450
status = self.get_provider_status()
451451
if status == ProviderStatus.NOT_READY:
452-
raise ProviderNotReadyError()
452+
return ProviderNotReadyError()
453453
if status == ProviderStatus.FATAL:
454-
raise ProviderFatalError()
454+
return ProviderFatalError()
455455
return None
456456

457457
def _before_hooks_and_merge_context(
@@ -511,7 +511,22 @@ async def evaluate_flag_details_async(
511511
)
512512

513513
try:
514-
self._assert_provider_status()
514+
if provider_err := self._assert_provider_status():
515+
error_hooks(
516+
flag_type,
517+
hook_context,
518+
provider_err,
519+
reversed_merged_hooks,
520+
hook_hints,
521+
)
522+
details = FlagEvaluationDetails(
523+
flag_key=flag_key,
524+
value=default_value,
525+
reason=Reason.ERROR,
526+
error_code=provider_err.error_code,
527+
error_message=provider_err.error_message,
528+
)
529+
return details
515530

516531
merged_context = self._before_hooks_and_merge_context(
517532
flag_type,
@@ -521,34 +536,39 @@ async def evaluate_flag_details_async(
521536
evaluation_context,
522537
)
523538

524-
flag_evaluation = await self._create_provider_evaluation_async(
539+
details = await self._create_provider_evaluation_async(
525540
provider,
526541
flag_type,
527542
flag_key,
528543
default_value,
529544
merged_context,
530545
)
546+
if err := details.get_exception():
547+
error_hooks(
548+
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
549+
)
550+
return details
531551

532552
after_hooks(
533553
flag_type,
534554
hook_context,
535-
flag_evaluation,
555+
details,
536556
reversed_merged_hooks,
537557
hook_hints,
538558
)
539559

540-
return flag_evaluation
560+
return details
541561

542562
except OpenFeatureError as err:
543563
error_hooks(flag_type, hook_context, err, reversed_merged_hooks, hook_hints)
544-
flag_evaluation = FlagEvaluationDetails(
564+
details = FlagEvaluationDetails(
545565
flag_key=flag_key,
546566
value=default_value,
547567
reason=Reason.ERROR,
548568
error_code=err.error_code,
549569
error_message=err.error_message,
550570
)
551-
return flag_evaluation
571+
return details
552572
# Catch any type of exception here since the user can provide any exception
553573
# in the error hooks
554574
except Exception as err: # pragma: no cover
@@ -559,20 +579,20 @@ async def evaluate_flag_details_async(
559579
error_hooks(flag_type, hook_context, err, reversed_merged_hooks, hook_hints)
560580

561581
error_message = getattr(err, "error_message", str(err))
562-
flag_evaluation = FlagEvaluationDetails(
582+
details = FlagEvaluationDetails(
563583
flag_key=flag_key,
564584
value=default_value,
565585
reason=Reason.ERROR,
566586
error_code=ErrorCode.GENERAL,
567587
error_message=error_message,
568588
)
569-
return flag_evaluation
589+
return details
570590

571591
finally:
572592
after_all_hooks(
573593
flag_type,
574594
hook_context,
575-
flag_evaluation,
595+
details,
576596
reversed_merged_hooks,
577597
hook_hints,
578598
)
@@ -607,7 +627,22 @@ def evaluate_flag_details(
607627
)
608628

609629
try:
610-
self._assert_provider_status()
630+
if provider_err := self._assert_provider_status():
631+
error_hooks(
632+
flag_type,
633+
hook_context,
634+
provider_err,
635+
reversed_merged_hooks,
636+
hook_hints,
637+
)
638+
flag_evaluation = FlagEvaluationDetails(
639+
flag_key=flag_key,
640+
value=default_value,
641+
reason=Reason.ERROR,
642+
error_code=provider_err.error_code,
643+
error_message=provider_err.error_message,
644+
)
645+
return flag_evaluation
611646

612647
merged_context = self._before_hooks_and_merge_context(
613648
flag_type,
@@ -624,6 +659,11 @@ def evaluate_flag_details(
624659
default_value,
625660
merged_context,
626661
)
662+
if err := flag_evaluation.get_exception():
663+
error_hooks(
664+
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
665+
)
666+
return flag_evaluation
627667

628668
after_hooks(
629669
flag_type,
@@ -693,27 +733,33 @@ async def _create_provider_evaluation_async(
693733
}
694734
get_details_callable = get_details_callables_async.get(flag_type)
695735
if not get_details_callable:
696-
raise GeneralError(error_message="Unknown flag type")
736+
return FlagEvaluationDetails(
737+
flag_key=flag_key,
738+
value=default_value,
739+
reason=Reason.ERROR,
740+
error_code=ErrorCode.GENERAL,
741+
error_message="Unknown flag type",
742+
)
697743

698744
resolution = await get_details_callable(
699745
flag_key=flag_key,
700746
default_value=default_value,
701747
evaluation_context=evaluation_context,
702748
)
703-
resolution.raise_for_error()
749+
if resolution.error_code:
750+
return resolution.to_flag_evaluation_details(flag_key)
704751

705752
# we need to check the get_args to be compatible with union types.
706-
_typecheck_flag_value(resolution.value, flag_type)
753+
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
754+
return FlagEvaluationDetails(
755+
flag_key=flag_key,
756+
value=resolution.value,
757+
reason=Reason.ERROR,
758+
error_code=err.error_code,
759+
error_message=err.error_message,
760+
)
707761

708-
return FlagEvaluationDetails(
709-
flag_key=flag_key,
710-
value=resolution.value,
711-
variant=resolution.variant,
712-
flag_metadata=resolution.flag_metadata or {},
713-
reason=resolution.reason,
714-
error_code=resolution.error_code,
715-
error_message=resolution.error_message,
716-
)
762+
return resolution.to_flag_evaluation_details(flag_key)
717763

718764
def _create_provider_evaluation(
719765
self,
@@ -743,27 +789,33 @@ def _create_provider_evaluation(
743789

744790
get_details_callable = get_details_callables.get(flag_type)
745791
if not get_details_callable:
746-
raise GeneralError(error_message="Unknown flag type")
792+
return FlagEvaluationDetails(
793+
flag_key=flag_key,
794+
value=default_value,
795+
reason=Reason.ERROR,
796+
error_code=ErrorCode.GENERAL,
797+
error_message="Unknown flag type",
798+
)
747799

748800
resolution = get_details_callable(
749801
flag_key=flag_key,
750802
default_value=default_value,
751803
evaluation_context=evaluation_context,
752804
)
753-
resolution.raise_for_error()
805+
if resolution.error_code:
806+
return resolution.to_flag_evaluation_details(flag_key)
754807

755808
# we need to check the get_args to be compatible with union types.
756-
_typecheck_flag_value(resolution.value, flag_type)
809+
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
810+
return FlagEvaluationDetails(
811+
flag_key=flag_key,
812+
value=resolution.value,
813+
reason=Reason.ERROR,
814+
error_code=err.error_code,
815+
error_message=err.error_message,
816+
)
757817

758-
return FlagEvaluationDetails(
759-
flag_key=flag_key,
760-
value=resolution.value,
761-
variant=resolution.variant,
762-
flag_metadata=resolution.flag_metadata or {},
763-
reason=resolution.reason,
764-
error_code=resolution.error_code,
765-
error_message=resolution.error_message,
766-
)
818+
return resolution.to_flag_evaluation_details(flag_key)
767819

768820
def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
769821
_event_support.add_client_handler(self, event, handler)
@@ -772,7 +824,9 @@ def remove_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
772824
_event_support.remove_client_handler(self, event, handler)
773825

774826

775-
def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
827+
def _typecheck_flag_value(
828+
value: typing.Any, flag_type: FlagType
829+
) -> typing.Optional[OpenFeatureError]:
776830
type_map: TypeMap = {
777831
FlagType.BOOLEAN: bool,
778832
FlagType.STRING: str,
@@ -782,6 +836,7 @@ def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
782836
}
783837
_type = type_map.get(flag_type)
784838
if not _type:
785-
raise GeneralError(error_message="Unknown flag type")
839+
return GeneralError(error_message="Unknown flag type")
786840
if not isinstance(value, _type):
787-
raise TypeMismatchError(f"Expected type {_type} but got {type(value)}")
841+
return TypeMismatchError(f"Expected type {_type} but got {type(value)}")
842+
return None

openfeature/flag_evaluation.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from dataclasses import dataclass, field
55

66
from openfeature._backports.strenum import StrEnum
7-
from openfeature.exception import ErrorCode
7+
from openfeature.exception import ErrorCode, OpenFeatureError
88

99
if typing.TYPE_CHECKING: # pragma: no cover
1010
# resolves a circular dependency in type annotations
@@ -56,6 +56,11 @@ class FlagEvaluationDetails(typing.Generic[T_co]):
5656
error_code: typing.Optional[ErrorCode] = None
5757
error_message: typing.Optional[str] = None
5858

59+
def get_exception(self) -> typing.Optional[OpenFeatureError]:
60+
if self.error_code:
61+
return ErrorCode.to_exception(self.error_code, self.error_message or "")
62+
return None
63+
5964

6065
@dataclass
6166
class FlagEvaluationOptions:
@@ -79,3 +84,14 @@ def raise_for_error(self) -> None:
7984
if self.error_code:
8085
raise ErrorCode.to_exception(self.error_code, self.error_message or "")
8186
return None
87+
88+
def to_flag_evaluation_details(self, flag_key: str) -> FlagEvaluationDetails[U_co]:
89+
return FlagEvaluationDetails(
90+
flag_key=flag_key,
91+
value=self.value,
92+
variant=self.variant,
93+
flag_metadata=self.flag_metadata,
94+
reason=self.reason,
95+
error_code=self.error_code,
96+
error_message=self.error_message,
97+
)

0 commit comments

Comments
 (0)