Skip to main content

Add your description here

Project description

Statsig OpenFeature Provider Python

Unofficial implementation of an OpenFeature Provider for Statsig compatible with their free tier. This implementation is currently quite rigid in where it pulls data from and only supports pulling boolean values from Feature Gates and all other types from Dynamic Configs.

The OpenFeature Evaluation Context is mapped to a StatsigUser which is then used for Feature Gate/Dynamic Config evaluation. The context's targeting_key is used as the User's ID and the context's attributes are passed into the custom fields of the user. If a targeting_key isn't present in the context then a default of "anonymous-user" will be used. You can provide an alternative fallback ID via the default_targeting_key arg of the constructor.

Usage

Initialization

You can initialize the client by either:

  1. Passing an sdk_key (optionally with some client_options)
  2. Passing an initialized Statsig client directly

This enables you to remove the direct dependency on statsig in your consuming code if needed.

client = Statsig(sdk_key="sdk-key-here", options=StatsigOptions(...))
client.initialize().wait()
provider = StatsigProvider(
    client=client,
    default_targeting_key="mystery-user",
    config_value_extractor_func=None,  # user default config value extractor 
)

Boolean

The provider will fetch a Feature Gate from Statsig using the flag_key, e.g.

from openfeature.api import get_client

if get_client().get_boolean_value("some-feature-gate-id", False):
    print("feature is enabled")

will fetch the some-feature-gate-id Feature Gate from Statsig if it exists. If it doesn't exist (i.e. Statsig's returned Feature Gate rule_id is None) then Statsig provide a default of False which will be returned from the value and the reason will be DEFAULT.

String

The provider will fetch a Dynamic Config from Statsig using the flag_key, e.g.

from openfeature.api import get_client

if val := get_client().get_string_value("some-dynamic-config-id", "foo"):
    print(f"flag value: {val}")

will fetch the some-dynamic-config-id Dynamic Config from Statsig if it exists. If it doesn't exist (i.e. Statsig's returned Dynamic Config rule_id is None) then Statsig provide a default of {} which will result in the default value being returned instead. Since Dynamic Configs are always an object, we have to extract the inner value from the config - this is done using the config_value_extractor_func, see the Config Value Extractors section for more info on how this works and how to define your own.

Integer

The provider will fetch a Dynamic Config from Statsig using the flag_key, e.g.

from openfeature.api import get_client

if val := get_client().get_integer_value("some-dynamic-config-id", 123):
    print(f"flag value: {val}")

will fetch the some-dynamic-config-id Dynamic Config from Statsig if it exists. If it doesn't exist (i.e. Statsig's returned Dynamic Config rule_id is None) then Statsig provide a default of {} which will result in the default value being returned instead. Since Dynamic Configs are always an object, we have to extract the inner value from the config - this is done using the config_value_extractor_func, see the Config Value Extractors section for more info on how this works and how to define your own.

Float

The provider will fetch a Dynamic Config from Statsig using the flag_key, e.g.

from openfeature.api import get_client

if val := get_client().get_float_value("some-dynamic-config-id", 1.23):
    print(f"flag value: {val}")

will fetch the some-dynamic-config-id Dynamic Config from Statsig if it exists. If it doesn't exist (i.e. Statsig's returned Dynamic Config rule_id is None) then Statsig provide a default of {} which will result in the default value being returned instead. Since Dynamic Configs are always an object, we have to extract the inner value from the config - this is done using the config_value_extractor_func, see the Config Value Extractors section for more info on how this works and how to define your own.

Object

The provider will fetch a Dynamic Config from Statsig using the flag_key, e.g.

from openfeature.api import get_client

if val := get_client().get_object_value("some-dynamic-config-id", []):
    print(f"flag value: {val}")

will fetch the some-dynamic-config-id Dynamic Config from Statsig if it exists. If it doesn't exist (i.e. Statsig's returned Dynamic Config rule_id is None) then Statsig provide a default of {} which will result in the default value being returned instead. Since Dynamic Configs are always an object, we have to extract the inner value from the config - this is done using the config_value_extractor_func, see the Config Value Extractors section for more info on how this works and how to define your own.

[!WARNING]
Even though the response from Statsig when fetching a Dynamic Config is an object, it doesn't support the use case for when the value is an array. Because of this, and to keep it consistent with the other methods it still relies on an embedded value existing by default. If you don't want this behaviour you can provide your own config_value_extractor_func.

Config Value Extractors

Since we can't return Dynamic Configs directly when evaluating flags for types we need a way of mapping the config object we receive into a value that aligns with the type the consumer is trying to fetch the flag value for. A default is provided with this implementation:

def default_config_value_extractor(config: dict) -> FlagValueType:
    """
    Extracts an embedded value from the config returned as long as there is only one (key, value) pair in the returned
    config. The key is irrelevant for this extractor.

    Args:
        config: the config that the value will be extracted from
    Returns:
        the value extracted from the config
    """
    values = list(config.values())
    if len(values) != 1:
        raise TypeMismatchError(
            "multiple keys found in config which isn't compatible with the default config value extractor, you can "
            "define your own config value extractor function and pass it in on provider initialization using the "
            "config_value_extractor_func kwarg"
        )

    val = values[0]
    if not isinstance(val, bool | int | float | str | Sequence | Mapping):
        raise TypeMismatchError("type of value extracted from statsig config is not a valid FlagValueType")

    return val

Which will take any object with a single key and pass the value directly to the caller, which will then be validated against the type of the flag being requested. e.g. any of the below configs would return the same value

{"foo": "bar"}
{"bar": "bar"}
{"some-other-key": "bar"}

If this implementation doesn't work for you, you can pass your own in as long as it has the Callable[[dict], FlagValueType] type. e.g. if you wanted to accept configs with multiple keys but you always extract from a specific key you could implement a naive solution like:

def static_key_value_extractor(config: dict) -> FlagValueType:
    return config.get("some-static-key", None)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

statsig_openfeature_provider_python-0.2.0.tar.gz (29.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file statsig_openfeature_provider_python-0.2.0.tar.gz.

File metadata

File hashes

Hashes for statsig_openfeature_provider_python-0.2.0.tar.gz
Algorithm Hash digest
SHA256 cbdda96704ea65582e72a55f027e92a80146ff03dc5f9211ab6a3c81f7893ddd
MD5 2f9a373a52426989529565f7b0018d69
BLAKE2b-256 52294acca7ed3662a99c84a8253e0767835aa9275f5d438584d93ea29519f339

See more details on using hashes here.

File details

Details for the file statsig_openfeature_provider_python-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for statsig_openfeature_provider_python-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e85f7a0c413fd1744afcbcea58535e7a3c9b897e1e8a7a98b51cdd25ec07db4a
MD5 62ca3451dbb36851f52098ae55b1525c
BLAKE2b-256 443e88b1b0813f472ace520756ec66613898e49b8c4cf9a5bbfa2409ae438210

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page