Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

rust_decider

Rust implementation of bucketing, exposures, targeting, overrides, and dynamic config logic.

Usage

class Decider

A class with these APIs:

  • choose(
       feature_name: str,
       context: Mapping[str, JsonValue]
    ) -> Decision
    
  • choose_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],
    ) -> bool
    
  • get_int(
      feature_name: str,
      context: Mapping[str, JsonValue],
    ) -> int
    
  • get_float(
      feature_name: str,
      context: Mapping[str, JsonValue],
    ) -> float
    
  • get_string(
      feature_name: str,
      context: Mapping[str, JsonValue],
    ) -> str
    
  • get_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,
    ) -> Feature
    

choose() examples:

from rust_decider import Decider
from rust_decider import DeciderException
from rust_decider import FeatureNotFoundException
from rust_decider import DeciderInitException
from rust_decider import PartialLoadException
from rust_decider import ValueTypeMismatchException

# initialize Decider instance
try:
    decider = Decider("../cfg.json")
except PartialLoadException as e:
    # log errors of misconfigured features
    print(f"{e.args[0]}: {e.args[2]}")

    # use partially initialized Decider instance
    decider = e.args[1]
except DeciderInitException as e:
    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"})
except DeciderException as e:
    print(e)

assert dict(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"
    ],
    "full_events": [...]
}

# `user_id` targeting not satisfied so "variant" is `None` in the returned Decision
try:
    decision = decider.choose(feature_name="exp_1", context={"user_id": "1"})
except DeciderException as e:
    print(e)

assert dict(decision) == {
  "variant": None,
  "value": None,
  "feature_id": 3246,
  "feature_name": "exp_1",
  "feature_version": 2,
  "events": [],
  "full_events": []
}

# handle "feature not found" exception
# (`FeatureNotFoundException` is a subclass of `DeciderException`)
try:
    decision = decider.choose(feature_name="not_here", context={"user_id": "1"})
except FeatureNotFoundException as e:
  print("handle feature not found exception:")
  print(e)
except DeciderException as e:
    print(e)

choose_all() examples:

# `decider` initialized same as above
decisions = decider.choose_all(context={"user_id": "3", "app_name": "ios"}, bucketing_field_filter="user_id")

assert dict(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"
  ],
  "full_events": [...]
}

class Exposer

Used to enable emitting expose v2 events in choose() API via expose or expose_holdout boolean params, e.g.:

from rust_decider import Decider
from rust_decider import Exposer

from baseplate.lib.events import EventQueue

serializer = lambda s: s.encode("utf-8")
eq = EventQueue(name="v2", event_serializer=serializer)
exposer = Exposer(expose_fn=eq.put)

decider_w_exposer = Decider("experiments.json", exposer)

# choose w/ expose
choice = decider_w_exposer.choose(
        feature_name="exp_name", 
        context={
           "user_id": "t2_abc",
           "user_is_employee": True,
           "other_info": { "arbitrary_field": "some_val" }
        },
       expose=True
)

# verify events were present to be emitted
len(choice.full_events)
dict(choice.full_events[0])
# `json_str` field contains json thrift schema encoded v2 event, which is passed to `expose_fn` of `Exposer` instance passed into `Decider``:

# {'decision_kind': 'FracAvail', 'exposure_key': 'decider_py_exposer_test:4:enabled:t2_88854', 'json_str': '{"1":{"str":"experiment"},"2":{"str":"expose"},"3":{"str":"user_id"},"5":{"i64":1697561871796},"6":{"str":"a6143b05-1e46-4c58-b6e8-4452c92dc1e7"},"8":{"str":"924c5785-b13f-4bf9-81e5-13ab6c89e794"},"107":{"rec":{"2":{"str":"ios"},"4":{"i32":0},"6":{"str":"us_en"}}},"108":{"rec":{"2":{"str":"d42b90e7-aae3-4ac5-b137-042de165ecf6"}}},"109":{"rec":{"17":{"str":"www.reddit.com"}}},"112":{"rec":{"1":{"str":"t2_88854"},"3":{"tf":1},"4":{"i64":1648859753},"16":{"tf":1}}},"114":{"rec":{"1":{"str":"t5_asdf"}}},"129":{"rec":{"1":{"i64":9787},"2":{"str":"decider_py_exposer_test"},"3":{"str":"redacted@reddit.com"},"4":{"str":"enabled"},"5":{"i64":1697561268},"6":{"i64":1698770868},"7":{"str":"user_id"},"8":{"str":"4"},"9":{"str":"t2_88854"},"10":{"tf":0}}},"500":{"rec":{"1":{"str":"UA"}}}}'}

If only holdouts are intended to be exposed, this can be accomplished via params:

choice = decider_w_exposer.choose(
    feature_name="exp_name", 
    context={
      ...
    },
    expose=False,
    expose_holdout=True
)

Dynamic Configurations + misc. examples:

# `decider` initialized same as above
try:
    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")
except FeatureNotFoundException as e:
    print("handle feature not found exception:")
    print(e)
except ValueTypeMismatchException as e:
    print("handle type mismatch:")
    print(e)
except DeciderException as e:
    print(e)

assert dc_bool == True
assert dc_int == 99
assert dc_float == 3.0
assert dc_string == "some_string"
assert dc_map == {
  "v": {
      "nested_map": {
          "w": False,
          "x": 1,
          "y": "some_string",
          "z": 3.0
      }
  },
  "w": False,
  "x": 1,
  "y": "some_string",
  "z": 3.0
}

assert dict(feature) == {
  "id": 3393,
  "name": "dc_bool",
  "version": 2,
  "bucket_val": '',
  "start_ts": 0,
  "stop_ts": 0,
  "emit_event": False
}

Dynamic Configuration all_values() example:

# `decider` initialized same as above
decisions = decider.all_values(context={})

assert decisions["dc_int"] == 99

python bindings used in Decider class

import rust_decider

# Init decider
decider = rust_decider.init("darkmode overrides targeting holdout mutex_group fractional_availability value", "../cfg.json")

# Bucketing needs a context
ctx = rust_decider.make_ctx({"user_id": "7"})

# Get a decision
choice = decider.choose("exp_1", ctx)
assert choice.err() is None # check for errors
choice.decision() # get the variant

# Get a dynamic config value
dc = decider.get_map("dc_map", ctx) # fetch a map DC
assert dc.err() is None # check for errors
dc.val() # get the actual map itself

Development

Updating package with latest src/lib.rs changes

# In a virtualenv, python >= 3.7
$ cd decider-py
$ pip install -r requirements-dev.txt
$ maturin develop

Running tests

$ pytest decider-py/test/

Publishing

Use conventional commit format in PR titles to trigger releases via release-please task in drone pipeline.

  • chore: & build: commits don't trigger releases (used for changes like updating config files or documentation)
  • fix: bumps the patch version
  • feat: bumps the minor version
  • feat!: bumps the major version

Cross-Compilation

We're using Zig 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

$ cargo fmt    --manifest-path decider-py/test/Cargo.toml
$ cargo clippy --manifest-path decider-py/test/Cargo.toml

Metadata

Release files for reddit-decider 1.19.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for reddit-decider 1.19.0b1
File
reddit_decider-1.19.0b1-cp37-abi3-musllinux_1_2_x86_64.whl CPython 3.7 abi3 Linux musl 1.2+ x86-64 Details
reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.7 abi3 Linux glibc 2.17+ x86-64 Details
reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.7 abi3 Linux glibc 2.17+ ARM64 Details
reddit_decider-1.19.0b1-cp37-abi3-macosx_11_0_arm64.whl CPython 3.7 abi3 macOS 11.0+ ARM64 Details
reddit_decider-1.19.0b1-cp37-abi3-macosx_10_7_x86_64.whl CPython 3.7 abi3 macOS 10.7+ x86-64 Details

Total release size: 2.1 MB

Release files / reddit_decider-1.19.0b1-cp37-abi3-musllinux_1_2_x86_64.whl

Download URL reddit_decider-1.19.0b1-cp37-abi3-musllinux_1_2_x86_64.whl
Size 448.5 kB
Tags CPython 3.7 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
c795d47632c6827172b6ba547d00178470897d524a21b7ae003fb7a3a071ff83
BLAKE2b-256 checksum
How to use checksums
20ae7e05aa0ea31b279b5dd030bd2765a58f23f44aebebb12942ee80717acfe1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.8

Release files / reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 449.9 kB
Tags CPython 3.7 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
8d8160f4d0707c00c02d7c80acf7bf122bef6d603de77b7bd60cb315dbd15bee
BLAKE2b-256 checksum
How to use checksums
da795b46861955ed0d2244bf040c2678071ea6451331367999d31c58353f6504
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.8

Release files / reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL reddit_decider-1.19.0b1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 440.9 kB
Tags CPython 3.7 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
29cf80965dd54c8e310da2854ef91e2cca60a09e4a8ab20423a4a72d7c881749
BLAKE2b-256 checksum
How to use checksums
a3766f2bc96958fd00e291fbab4202bb135c1ead10d1ac7ba3a1157dde1e3a53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.8

Release files / reddit_decider-1.19.0b1-cp37-abi3-macosx_11_0_arm64.whl

Download URL reddit_decider-1.19.0b1-cp37-abi3-macosx_11_0_arm64.whl
Size 394.6 kB
Tags CPython 3.7 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
006e3a081d80721470c1e0d6ace5a73fdfe081b2c8a57fb3f2c512e84f78dd55
BLAKE2b-256 checksum
How to use checksums
e2eb32fe03f87b5a391af6bce941c85f829bd2a05d0ce51f754a10f8886297d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.8

Release files / reddit_decider-1.19.0b1-cp37-abi3-macosx_10_7_x86_64.whl

Download URL reddit_decider-1.19.0b1-cp37-abi3-macosx_10_7_x86_64.whl
Size 409.6 kB
Tags CPython 3.7 abi3 macOS 10.7+ x86-64
SHA-256 checksum
How to use checksums
91fad1e788177e4283ce85a0a9e7828489788218843445c6b25b1aa96b9bea4a
BLAKE2b-256 checksum
How to use checksums
822d76f192a4dc1f60ccbd6e7c823167805058ba9c0fd697cc0e26b9eab5fb62
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.8

Release history Release notifications | RSS feed

This release

1.19.0b1 This release

5 release files

1.18.2

5 release files

1.18.1

5 release files

1.16.4

5 release files

1.16.3

5 release files

1.16.2

5 release files

1.14.0

6 release files

1.13.0

6 release files

1.12.0

6 release files

1.11.0

6 release files

1.7.0

6 release files

1.6.0

6 release files

1.5.8

6 release files

1.5.7

6 release files

1.5.6

5 release files

1.5.5

5 release files

1.5.4

5 release files

1.5.3

5 release files

1.5.2

5 release files

1.5.1

5 release files

1.5.0

5 release files

1.4.2

5 release files

1.4.1

5 release files

1.4.0

5 release files

1.3.1

5 release files

1.3.0

5 release files

1.2.31

5 release files

1.2.30

5 release files

1.2.29

5 release files

1.2.24

5 release files

1.2.20

5 release files

1.2.19

5 release files

1.2.18

5 release files

1.2.17

5 release files

1.2.16

5 release files

1.2.15

5 release files

1.2.9

5 release files

1.2.8

5 release files

1.2.7

5 release files

1.2.6

5 release files

1.2.5

5 release files

1.2.4

5 release files

1.2.3

5 release files

1.2.2

5 release files

1.2.1

5 release files

1.2.0

5 release files

1.1.24

5 release files

1.1.23

5 release files

1.1.22

5 release files

1.1.21

5 release files

1.1.20

5 release files

1.1.19

5 release files

1.1.18

5 release files

1.1.17

5 release files

1.1.16

5 release files

1.1.15

5 release files

1.1.11

5 release files

1.1.10

5 release files

1.1.9

5 release files

1.1.8

5 release files

1.1.7

5 release files

1.1.6

5 release files

1.1.5

5 release files

1.1.4

5 release files

1.1.3

5 release files

1.1.2

5 release files

1.1.1

5 release files

1.1.0

5 release files

1.0.19

5 release files

1.0.18

5 release files

1.0.17

5 release files

1.0.16

5 release files

1.0.15

5 release files

1.0.14

1 release file

1.0.13

1 release file

1.0.12

2 release files

1.0.11

1 release file

1.0.9

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page