Package: reddit-decider
rust_deciderRust implementation of bucketing, targeting, overrides, and dynamic config logic.UsageclassDeciderA class used to expose these APIs:choose(
   feature_name: str,
   context: Mapping[str, JsonValue]
) -> Decisionchoose_all(
   context: Mapping[str, JsonValue],
   bucketing_field_filter: Optional[str] = None
) -> Dict[str, Decision](dynamic configurations)get_bool(
  feature_name: str,
  context: Mapping[str, JsonValue],
) -> boolget_int(
  feature_name: str,
  context: Mapping[str, JsonValue],
) -> intget_float(
  feature_name: str,
  context: Mapping[str, JsonValue],
) -> floatget_string(
  feature_name: str,
  context: Mapping[str, JsonValue],
) -> strget_map(
  feature_name: str,
  context: Mapping[str, JsonValue],
) -> Dict[str, Any]all_values(
   context: Mapping[str, JsonValue],
) -> Dict[str, Any]misc:get_feature(
  feature_name: str,
) -> Featurechoose()examples:fromrust_deciderimportDeciderfromrust_deciderimportDeciderExceptionfromrust_deciderimportFeatureNotFoundExceptionfromrust_deciderimportDeciderInitExceptionfromrust_deciderimportPartialLoadExceptionfromrust_deciderimportValueTypeMismatchException# initialize Decider instancetry:decider=Decider("../cfg.json")exceptPartialLoadExceptionase:# log errors of misconfigured featuresprint(f"{e.args[0]}:{e.args[2]}")# use partially initialized Decider instancedecider=e.args[1]exceptDeciderInitExceptionase:print(e)# get a Decision for a feature via choose()try:decision=decider.choose(feature_name="exp_1",context={"user_id":"3","app_name":"ios"})exceptDeciderExceptionase:print(e)assertdict(decision)=={"variant":"variant_0","value":None,"feature_id":3246,"feature_name":"exp_1","feature_version":2,"events":["0::::3246::::exp_1::::2::::variant_0::::3::::user_id::::37173982::::2147483648::::test"]}# `user_id` targeting not satisfied so "variant" is `None` in the returned Decisiontry:decision=decider.choose(feature_name="exp_1",context={"user_id":"1"})exceptDeciderExceptionase:print(e)assertdict(decision)=={"variant":None,"value":None,"feature_id":3246,"feature_name":"exp_1","feature_version":2,"events":[]}# handle "feature not found" exception# (`FeatureNotFoundException` is a subclass of `DeciderException`)try:decision=decider.choose(feature_name="not_here",context={"user_id":"1"})exceptFeatureNotFoundExceptionase:print("handle feature not found exception:")print(e)exceptDeciderExceptionase:print(e)choose_all()examples:# `decider` initialized same as abovedecisions=decider.choose_all(context={"user_id":"3","app_name":"ios"},bucketing_field_filter="user_id")assertdict(decisions["exp_67"])=={"variant":"variant_0","value":None,"feature_id":3125,"feature_name":"exp_67","feature_version":4,"events":["0::::3125::::exp_67::::4::::variant_0::::3::::user_id::::37173982::::2147483648::::test"]}Dynamic Configurations + misc. examples:# `decider` initialized same as abovetry:dc_bool=decider.get_bool("dc_bool",context={})dc_int=decider.get_int("dc_int",context={})dc_float=decider.get_float("dc_float",context={})dc_string=decider.get_string("dc_string",context={})dc_map=decider.get_map("dc_map",context={})feature=decider.get_feature("dc_map")exceptFeatureNotFoundExceptionase:print("handle feature not found exception:")print(e)exceptValueTypeMismatchExceptionase:print("handle type mismatch:")print(e)exceptDeciderExceptionase:print(e)assertdc_bool==Trueassertdc_int==99assertdc_float==3.0assertdc_string=="some_string"assertdc_map=={"v":{"nested_map":{"w":False,"x":1,"y":"some_string","z":3.0}},"w":False,"x":1,"y":"some_string","z":3.0}assertdict(feature)=={"id":3393,"name":"dc_bool","version":2,"bucket_val":'',"start_ts":0,"stop_ts":0,"owner":"test","emit_event":False}Dynamic Configurationall_values()example:# `decider` initialized same as abovedecisions=decider.all_values(context={})assertdecisions["dc_int"]==99python bindings used inDeciderclassimportrust_decider# Init deciderdecider=rust_decider.init("darkmode overrides targeting holdout mutex_group fractional_availability value","../cfg.json")# Bucketing needs a contextctx=rust_decider.make_ctx({"user_id":"7"})# Get a decisionchoice=decider.choose("exp_1",ctx)assertchoice.err()isNone# check for errorschoice.decision()# get the variant# Get a dynamic config valuedc=decider.get_map("dc_map",ctx)# fetch a map DCassertdc.err()isNone# check for errorsdc.val()# get the actual map itselfDevelopmentUpdating package with latestsrc/lib.rschanges# In a virtualenv, python >= 3.7$cddecider-py
$pipinstall-rrequirements-dev.txt
$maturindevelopRunning tests$pytestdecider-py/test/PublishingUseconventional commit formatin PR titles to trigger releases viarelease-pleasetask in drone pipeline.chore:&build:commits don't trigger releases (used for changes like updating config files or documentation)fix:bumps the patch versionfeat:bumps the minor versionfeat!:bumps the major versionCross-CompilationWe'reusingZig for cross-compilation which is reflected in the switch from the "FLAVOR" approach to "TARGET". Cross-compilation is useful when we want to build code on one type of machine (like our CI server), but have it run on a different type of machine (like a server or user's machine with a different architecture).To build wheels for multiple platforms more effectively, we use "TARGET" variable in the .drone.yml. This includes platforms like "linux-aarch64" and "linux-musl-x86_64".Formatting / Linting$cargofmt--manifest-pathdecider-py/test/Cargo.toml
$cargoclippy--manifest-pathdecider-py/test/Cargo.toml