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.0b2

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.0b2
File
reddit_decider-1.19.0b2-cp37-abi3-musllinux_1_2_x86_64.whl CPython 3.7 abi3 Linux musl 1.2+ x86-64 Details
reddit_decider-1.19.0b2-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.0b2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.7 abi3 Linux glibc 2.17+ ARM64 Details
reddit_decider-1.19.0b2-cp37-abi3-macosx_11_0_arm64.whl CPython 3.7 abi3 macOS 11.0+ ARM64 Details
reddit_decider-1.19.0b2-cp37-abi3-macosx_10_7_x86_64.whl CPython 3.7 abi3 macOS 10.7+ x86-64 Details

Total release size: 2.2 MB

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

Download URL reddit_decider-1.19.0b2-cp37-abi3-musllinux_1_2_x86_64.whl
Size 455.1 kB
Tags CPython 3.7 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
7ce205bf83e642e7387c6ae5d4269114dfdd7a71f0fecdb8d5ea71d2462155ca
BLAKE2b-256 checksum
How to use checksums
4909bb07d26552a2eaec93bffcae6c8e914c0f5ca18483f6cbc0b2733a6a0c38
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.0b2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL reddit_decider-1.19.0b2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 456.5 kB
Tags CPython 3.7 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
394b21b740ec3f14e5968438a598fe16af5d9e98f588e1747eec08ccff81a7f4
BLAKE2b-256 checksum
How to use checksums
8cb92801b2f9ddaebc60b06265d3b7ef9c8f5242312fde2c46c1c8f4b467b5eb
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.0b2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL reddit_decider-1.19.0b2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 446.8 kB
Tags CPython 3.7 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
f35b5ef9ee829ec7be754c032e7acbc8e61f98c6817c159b4385f2a6e36e7945
BLAKE2b-256 checksum
How to use checksums
354e0a81c26d5b6b91cdebc1da06f048d305d6e3872a1f0b432eabd1b5ae9ed5
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.0b2-cp37-abi3-macosx_11_0_arm64.whl

Download URL reddit_decider-1.19.0b2-cp37-abi3-macosx_11_0_arm64.whl
Size 400.5 kB
Tags CPython 3.7 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1b913e75a3d225db203ab02d220538589047fe3b66b3a333b51082d48ca81ef0
BLAKE2b-256 checksum
How to use checksums
2eaf03474e77518b96eb92218171505214acf1d06f7a4d6d6659ca2bfe12b1c9
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.0b2-cp37-abi3-macosx_10_7_x86_64.whl

Download URL reddit_decider-1.19.0b2-cp37-abi3-macosx_10_7_x86_64.whl
Size 415.6 kB
Tags CPython 3.7 abi3 macOS 10.7+ x86-64
SHA-256 checksum
How to use checksums
8b38ccbd87b5a1242af05cca8771c9a0736fd37dc8a335d83ce2a0ebe1e99eb2
BLAKE2b-256 checksum
How to use checksums
9ea040e3848ff18614be0c6efaaefce8cc6eabbccd087fdbf1fe76bd9a02a6ca
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.0b2 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