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

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

Total release size: 4.3 MB

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

Download URL reddit_decider-1.19.0b4-cp37-abi3-musllinux_1_2_x86_64.whl
Size 897.4 kB
Tags CPython 3.7 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
b1034e5b0cc5c495392fe30db1414cd8fbe101128db0a2993aaf9f8f04039e04
BLAKE2b-256 checksum
How to use checksums
3cb84da469a8ea34952f2698c63ccf45887a45990de213b6f255374a61dbd5f4
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.0b4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL reddit_decider-1.19.0b4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 898.8 kB
Tags CPython 3.7 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
488e70e49453bbe85a332ebdd8d2656c87efbb30d903abd954dcbeace1bc03e1
BLAKE2b-256 checksum
How to use checksums
ba05ce6977a290d8563571a92ebe8467144d0e8278adad8410c4b4fee1a7d6db
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.0b4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL reddit_decider-1.19.0b4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 868.8 kB
Tags CPython 3.7 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
e676a9322305b221c97ed4cb63101fdcf6e16ecabac2c187e8f6eb969c5a74cb
BLAKE2b-256 checksum
How to use checksums
6663bf64328132537ccfea91aa0cff9c8aa33aad042c547fab845c02e0786d52
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.0b4-cp37-abi3-macosx_11_0_arm64.whl

Download URL reddit_decider-1.19.0b4-cp37-abi3-macosx_11_0_arm64.whl
Size 776.1 kB
Tags CPython 3.7 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
63c7d214589c6f699beac001df65af1bc1b7ed2d41351f7bccd5542475645b57
BLAKE2b-256 checksum
How to use checksums
7c37fec03fae37800db5eea5f261ee34b578fca21493c84f9d64879108484b6d
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.0b4-cp37-abi3-macosx_10_7_x86_64.whl

Download URL reddit_decider-1.19.0b4-cp37-abi3-macosx_10_7_x86_64.whl
Size 813.2 kB
Tags CPython 3.7 abi3 macOS 10.7+ x86-64
SHA-256 checksum
How to use checksums
6f95fb2272a075b2aa64317f0842c58c9d697e1523510eb19057d44d647f3861
BLAKE2b-256 checksum
How to use checksums
7033654cc9d5b695233303a2a72e517449e746f6b570d68da836760ea2d52b1e
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.0b4 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