Skip to main content

controlvar-openfeature (Python)

Beta. This is a 0.x release: pin the exact version you test against.

OpenFeature provider for Controlvar. Flags are evaluated by the server through the controlvar Python SDK (GET /api/v1/feature-flags/evaluate/{flag_key}?user_id=<targeting_key>&context=<attributes>) and cached per user + flag; nothing is bucketed locally unless you pass a client= built with evaluation="local". Boolean, string, integer, float and object calls are answered.

Full documentation: the OpenFeature provider page.

Install

To install it, follow the OpenFeature provider page.

Requires Python 3.10+ and openfeature-sdk 0.10.0 or later. The only other dependency is controlvar, which itself has none.

Quick start

import os
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from controlvar_openfeature import ExperimentationProvider

provider = ExperimentationProvider(
    api_key=os.environ["CONTROLVAR_API_KEY"],
    base_url=os.environ.get("CONTROLVAR_API_URL", "http://localhost:8000"),
    cache_ttl=300,   # seconds a successful evaluation is reused per user + flag
    timeout=10,      # seconds
)
api.set_provider(provider)
client = api.get_client()

# targeting_key == platform user_id (required); the attributes go to the flag's targeting rules.
ctx = EvaluationContext(targeting_key="user-12345", attributes={"plan": "enterprise"})
enabled = client.get_boolean_value("dark-mode", False, ctx)                       # -> enabled
label = client.get_string_value("checkout-button-label", "Buy now", ctx)          # -> the flag's string value
max_items = client.get_integer_value("cart-max-items", 10, ctx)                   # -> the flag's number value
theme = client.get_object_value("checkout-theme", {"color": "blue"}, ctx)         # -> the flag's JSON value

# Experiments and event tracking are not part of OpenFeature: use the SDK client underneath.
assignment = provider.client.get_assignment("checkout_flow", "user-12345")
provider.client.track("user-12345", "purchase", event_value=49.99, experiment_key="checkout_flow")

Constructor: ExperimentationProvider(api_key, base_url="http://localhost:8000", cache_ttl=300, timeout=10, *, client=None, transport=None). Pass client= to share one controlvar.ExperimentationClient (and its cache) with the rest of your application.

Resolution rules

OpenFeature call Reads Value
get_boolean_* a boolean flag enabled
get_string_* a string flag the flag's value
get_integer_* a number flag the flag's value when it has no fractional part (10 or 10.0 answers 10)
get_float_* a number flag the flag's value as a float (10 answers 10.0)
get_object_* a json flag a new copy of the flag's value (an object or an array)

The flag's value is its on value when the flag is on for the user, and its off value otherwise, an inactive flag included. Nothing is coerced: a call of another type gets your default, a boolean call on a string, number or json flag included.

The server's answer Value Reason Variant
reason: "targeting_rule", on / off the on value / the off value TARGETING_MATCH on / off
reason: "rollout", on / off the on value / the off value SPLIT on / off
reason: "inactive" the off value, not your default DISABLED off
answered from the SDK's cache as cached CACHED as cached
reason: "error" your default ERROR, GENERAL not set
a flag of another type than the call your default ERROR, TYPE_MISMATCH not set
no value (a string, integer, float or object call; a server or an SDK without flag values) your default ERROR, GENERAL not set
a value that is not of the flag's type your default ERROR, PARSE_ERROR not set

That table is for every call type. A boolean call reads enabled, so an answer with no value_type (from a server without flag values, where every flag is boolean) answers it rather than your default. A server reason the provider does not know answers the value with reason UNKNOWN. An integer call on a number with a fractional part (2.5) answers TYPE_MISMATCH, and bool is never a number.

  • targeting_key → user_id. It is required: without it you get the default value with TARGETING_KEY_MISSING.
  • The context's attributes are the user's attributes: the evaluate call carries them as its context, for the flag's targeting rules. A datetime, at any depth, goes as an ISO 8601 string (a naive one is taken as UTC).
  • By default the SDK caches an answer per user + flag key, and the attributes are not part of that key: within cache_ttl a second call for the same targeting_key and flag gets the first answer (reason CACHED), whatever its attributes. A changed attribute is seen once that answer expires; cache_ttl=0 caches nothing, and provider.client.clear_cache() drops every answer.
  • A client= built with evaluation="local" answers each call it can from its ruleset, using that call's attributes, and such an answer is never CACHED.
  • Errors never raise and are never cached: 404 (an unknown flag key) → FLAG_NOT_FOUND; anything else (401, 422, 5xx, a 2xx whose body is not an evaluation, timeout, network) → GENERAL. Both return your default with reason ERROR.
  • client.track(event_name, ctx, TrackingEventDetails(value=…)) forwards to ExperimentationClient.track (an event with no key is fanned out by the Python SDK's rule).

Argument order

OpenFeature's calls take your default second: client.get_string_value("checkout-button-label", "Buy now", ctx). The SDK client underneath, provider.client, has typed getters of its own that take the user second and the default as a keyword: provider.client.get_string_value("checkout-button-label", "user-12345", {"plan": "enterprise"}, default="Buy now"). default is keyword-only, so a call written in OpenFeature's order on provider.client raises TypeError instead of evaluating the flag for a user named "Buy now".

Backend endpoints used

Every request carries X-API-Key: <key>, Content-Type: application/json, Accept: application/json.

Call Method and path Response used
every resolve_*_details GET /api/v1/feature-flags/evaluate/{flag_key}?user_id=…[&context=…] {key, enabled, value, value_type, config, reason}; off with reason: "inactive" when the flag exists but is not ACTIVE; 404 only for an unknown key
provider.client.track with a key POST /api/v1/tracking/track ignored
provider.track, and provider.client.track without a key POST /api/v1/tracking/batch, one entry per experiment and flag it reaches; with none, POST /api/v1/tracking/track with no key ignored
provider.client.track_batch POST /api/v1/tracking/batch {success_count, failure_count, errors}
provider.client.get_assignment POST /api/v1/tracking/assign {experiment_key, user_id, variant_id, variant_name, is_control, configuration}

Development

Install the provider and the Python SDK from the root of a clone of this repository (openfeature-sdk is installed as a dependency):

pip install -e sdk/python -e sdk/openfeature-python

To run the tests:

source venv/bin/activate && pip install openfeature-sdk
python -m pytest sdk/openfeature-python/tests -q -o addopts="" -p no:cacheprovider

tests/conftest.py puts sdk/python and src/ on sys.path, so neither package needs to be installed.

Contract smoke

CONTROLVAR_API_KEY=<key> python sdk/openfeature-python/examples/contract_smoke.py

It prints:

{"sdk":"openfeature-python","assign":{"variant_name":"control","is_control":true,"sticky":true},"flag":{"enabled":true},"track":{"ok":true},"fanout":{"ok":true}}

Env: CONTROLVAR_API_URL (default http://localhost:8000), CONTROLVAR_API_KEY (required), CONTRACT_EXPERIMENT_KEY (default sdk_contract_ab), CONTRACT_FLAG_KEY (default sdk_contract_flag), CONTRACT_USER_ID (default random smoke-<uuid>). The flag is resolved through the OpenFeature API; assignment and tracking use provider.client.

CI runs this SDK's contract smoke against a real API: openfeature-python is in the sdks: list of the SDK Live Contract job in pr-qa-gate.yml. Run it with python tests/sdk-contract/live/run_live_contract.py --sdk openfeature-python --strict.

Metadata

Release files for controlvar-openfeature 0.1.0

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

Source distribution (sdist)

Source distribution for controlvar-openfeature 0.1.0
File Size Uploaded Provenance Actions
controlvar_openfeature-0.1.0.tar.gz 27.2 kB With provenance Details for controlvar_openfeature-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for controlvar-openfeature 0.1.0
File Interpreter ABI Platform Actions
controlvar_openfeature-0.1.0-py3-none-any.whl Python 3 none any Details for controlvar_openfeature-0.1.0-py3-none-any.whl

Total release size: 40.6 kB

Release files / controlvar_openfeature-0.1.0.tar.gz

Download URL controlvar_openfeature-0.1.0.tar.gz
Size 27.2 kB
Tags Source
SHA-256 checksum
How to use checksums
14d77d25c2cc2ccf1d86a09c6f3430fe3a5d3700983a1590c848518ea5db1bdb
BLAKE2b-256 checksum
How to use checksums
3675053dbf397856557582632b518f6a1c8c35e1175bc28c40d2cd51b7febce7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Release files / controlvar_openfeature-0.1.0-py3-none-any.whl

Download URL controlvar_openfeature-0.1.0-py3-none-any.whl
Size 13.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c78761d8507fc37e5b7a5da666fd2124ff7ef09eee715edff2451179b33b1d63
BLAKE2b-256 checksum
How to use checksums
233feeda0a2ae9a2e4d1ce98fc44d28d6b678e58f56b86d19dc1d85b0cf8de6f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Security for controlvar-openfeature 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for controlvar_openfeature-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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