1010from typing import IO
1111from typing import List
1212from typing import Optional
13+ from typing import Type
14+ from typing import TypeVar
1315from typing import Union
1416
1517from baseplate import RequestContext
2729from rust_decider import Decision
2830from rust_decider import FeatureNotFoundException
2931from rust_decider import make_ctx
32+ from rust_decider import ValueTypeMismatchException
3033from typing_extensions import Literal
3134
3235
3336logger = logging .getLogger (__name__ )
3437
3538EMPLOYEE_ROLES = ["employee" , "contractor" ]
3639IDENTIFIERS = ["user_id" , "device_id" , "canonical_url" ]
40+ TYPE_STR_LOOKUP = {bool : "boolean" , int : "integer" , float : "float" , str : "string" , dict : "map" }
3741
3842
3943class EventType (Enum ):
@@ -58,6 +62,8 @@ class DeciderContext:
5862 :code:`DeciderContext()` is populated in :code:`make_object_for_context()`.
5963 """
6064
65+ T = TypeVar ("T" )
66+
6167 def __init__ (
6268 self ,
6369 user_id : Optional [str ] = None ,
@@ -173,7 +179,7 @@ def __init__(
173179 event_logger : Optional [EventLogger ] = None ,
174180 ):
175181 self ._decider_context = decider_context
176- self ._internal = internal
182+ self ._internal : RustDecider = internal
177183 self ._span = server_span
178184 self ._context_name = context_name
179185 if event_logger :
@@ -696,28 +702,6 @@ def get_all_variants_for_identifier_without_expose(
696702
697703 return parsed_choices
698704
699- def _get_dynamic_config_value (
700- self ,
701- feature_name : str ,
702- decider_func : Callable [[str , DeciderContext ], Any ],
703- default : Any ,
704- ) -> Optional [Any ]:
705- ctx = self ._get_ctx ()
706- ctx_err = ctx .err ()
707- if ctx_err is not None :
708- logger .info (f"Encountered error in rust_decider.make_ctx(): { ctx_err } " )
709- return None
710-
711- res = decider_func (feature_name , ctx )
712- if res is None :
713- return default
714- error = res .err ()
715- if error :
716- logger .warning (f"Encountered error { decider_func .__name__ } : { error } " )
717- return default
718-
719- return res .val ()
720-
721705 def get_bool (self , feature_name : str , default : bool = False ) -> bool :
722706 """Fetch a Dynamic Configuration of boolean type.
723707
@@ -728,10 +712,7 @@ def get_bool(self, feature_name: str, default: bool = False) -> bool:
728712
729713 :return: the boolean value of the dyanimc config if it is active/exists, :code:`default` parameter otherwise.
730714 """
731- decider = self ._get_decider ()
732- if not decider :
733- return default
734- return self ._get_dynamic_config_value (feature_name , decider .get_bool , default )
715+ return self ._get_dynamic_config_value (feature_name , default , bool , self ._internal .get_bool )
735716
736717 def get_int (self , feature_name : str , default : int = 0 ) -> int :
737718 """Fetch a Dynamic Configuration of int type.
@@ -743,10 +724,7 @@ def get_int(self, feature_name: str, default: int = 0) -> int:
743724
744725 :return: the int value of the dyanimc config if it is active/exists, :code:`default` parameter otherwise.
745726 """
746- decider = self ._get_decider ()
747- if not decider :
748- return default
749- return self ._get_dynamic_config_value (feature_name , decider .get_int , default )
727+ return self ._get_dynamic_config_value (feature_name , default , int , self ._internal .get_int )
750728
751729 def get_float (self , feature_name : str , default : float = 0.0 ) -> float :
752730 """Fetch a Dynamic Configuration of float type.
@@ -758,10 +736,9 @@ def get_float(self, feature_name: str, default: float = 0.0) -> float:
758736
759737 :return: the float value of the dyanimc config if it is active/exists, :code:`default` parameter otherwise.
760738 """
761- decider = self ._get_decider ()
762- if not decider :
763- return default
764- return self ._get_dynamic_config_value (feature_name , decider .get_float , default )
739+ return self ._get_dynamic_config_value (
740+ feature_name , default , float , self ._internal .get_float
741+ )
765742
766743 def get_string (self , feature_name : str , default : str = "" ) -> str :
767744 """Fetch a Dynamic Configuration of string type.
@@ -773,10 +750,7 @@ def get_string(self, feature_name: str, default: str = "") -> str:
773750
774751 :return: the string value of the dyanimc config if it is active/exists, :code:`default` parameter otherwise.
775752 """
776- decider = self ._get_decider ()
777- if not decider :
778- return default
779- return self ._get_dynamic_config_value (feature_name , decider .get_string , default )
753+ return self ._get_dynamic_config_value (feature_name , default , str , self ._internal .get_string )
780754
781755 def get_map (self , feature_name : str , default : Optional [dict ] = None ) -> Optional [dict ]:
782756 """Fetch a Dynamic Configuration of map type.
@@ -788,10 +762,7 @@ def get_map(self, feature_name: str, default: Optional[dict] = None) -> Optional
788762
789763 :return: the map value of the dyanimc config if it is active/exists, :code:`default` parameter otherwise.
790764 """
791- decider = self ._get_decider ()
792- if not decider :
793- return default
794- return self ._get_dynamic_config_value (feature_name , decider .get_map , default )
765+ return self ._get_dynamic_config_value (feature_name , default , dict , self ._internal .get_map )
795766
796767 def get_all_dynamic_configs (self ) -> List [Dict [str , Any ]]:
797768 """Return a list of dynamic configuration dicts in this format:
@@ -826,40 +797,61 @@ def get_all_dynamic_configs(self) -> List[Dict[str, Any]]:
826797
827798 :return: list of all active dynamic config dicts.
828799 """
829- decider = self ._get_decider ()
830- if not decider :
831- return []
832-
833- ctx = self ._get_ctx ()
834- ctx_err = ctx .err ()
835- if ctx_err is not None :
836- logger .info (f"Encountered error in rust_decider.make_ctx(): { ctx_err } " )
800+ if self ._internal is None :
801+ logger .error ("rs_decider is None--did not initialize." )
837802 return []
838803
839- all_decisions_result = decider . get_all_values ( ctx )
804+ ctx = self . _decider_context . to_dict ( )
840805
841- error = all_decisions_result .err ()
842- if error :
843- logger .info (f"Encountered error in decider.choose_all(): { error } " )
806+ try :
807+ values = self ._internal .all_values (ctx )
808+ except DeciderException as exc :
809+ logger .info (str (exc ))
844810 return []
845811
846- all_decisions = all_decisions_result .decisions ()
847812 parsed_configs = []
848813
849- for dc_name , decision in all_decisions .items ():
850- decision_error = decision .err ()
851- if decision_error :
852- logger .info (
853- f"Encountered error for dynamic config: { dc_name } in decider.get_all_values(): { decision_error } "
854- )
855- continue
814+ for feature_name , val in values .items ():
815+ parsed_configs .append (self ._value_to_dc_dict (feature_name , val ))
816+
817+ return parsed_configs
856818
857- value_dict = decision .value_dict ()
819+ def _get_dynamic_config_value (
820+ self ,
821+ feature_name : str ,
822+ default : Any ,
823+ dc_type : Type [T ],
824+ get_fn : Callable [..., Type [T ]],
825+ ) -> T :
826+ if self ._internal is None :
827+ logger .error ("rs_decider is None--did not initialize." )
828+ return default
858829
859- if value_dict :
860- parsed_configs .append (value_dict )
830+ ctx = self ._decider_context .to_dict ()
861831
862- return parsed_configs
832+ try :
833+ value = get_fn (feature_name = feature_name , context = ctx )
834+ except FeatureNotFoundException as exc :
835+ warnings .warn (str (exc ))
836+ return default
837+ except ValueTypeMismatchException as exc :
838+ logger .info (str (exc ))
839+ return default
840+ except DeciderException as exc :
841+ logger .info (str (exc ))
842+ return default
843+
844+ try :
845+ return dc_type (value ) # type: ignore [call-arg]
846+ except TypeError :
847+ return default
848+
849+ def _value_to_dc_dict (self , feature_name : str , value : Optional [Any ]) -> Dict [str , Any ]:
850+ return {
851+ "name" : feature_name ,
852+ "value" : value ,
853+ "type" : "" if value is None else TYPE_STR_LOOKUP [type (value )],
854+ }
863855
864856 def get_experiment (self , experiment_name : str ) -> Optional [ExperimentConfig ]:
865857 """Get an :py:class:`~reddit_decider.ExperimentConfig` `dataclass <https://github.com/reddit/experiments.py/blob/develop/reddit_decider/__init__.py#L44>`_
0 commit comments