python-sdk
Vendor-agnostic feature flagging API for Python, letting you swap flag providers without touching call sites.
Repository Health
Technical Analysis
OpenFeature Python SDK is the reference implementation of the CNCF OpenFeature specification for Python applications, providing a single, vendor-agnostic API for evaluating boolean, string, integer, float, and object feature flags. Instead of coupling application code to a specific flag-management vendor’s SDK, teams write against OpenFeature’s client interface and swap the underlying FeatureProvider implementation — from a no-op stub to an in-memory provider to any of the community and vendor-built providers in the OpenFeature ecosystem — without touching call sites.
Beyond flag evaluation, the SDK implements the full OpenFeature specification surface: hooks for cross-cutting logic at each stage of evaluation, evaluation context and transaction context propagation (with a ready-made ContextVarsTransactionContextPropagator for threads and asyncio), a domain system for binding multiple providers within one process, an eventing system for reacting to provider state changes, and a tracking API for tying user actions back to flag evaluations for experimentation.
What You Get
- A typed client (OpenFeatureClient) with sync and async methods for boolean, string, integer, float, and object flag evaluation, each with a plain-value and full-details variant
- A provider abstraction (FeatureProvider / AbstractProvider) so any flag-management backend, from an in-memory stub to a commercial vendor’s provider, can be swapped in without changing call sites
- A hook system (before/after/error/finally_after) for adding cross-cutting logic like logging, metrics, or validation at defined points in the evaluation lifecycle
- Evaluation context and transaction context propagation, including a ContextVarsTransactionContextPropagator that works across threads and asyncio for request-scoped targeting data
- A domain system for binding different clients to different providers within the same process, plus an eventing API for reacting to provider readiness and state changes
Common Use Cases
- Vendor migration - Teams switching feature-flag vendors swap the registered FeatureProvider implementation instead of rewriting every client.get_boolean_value() call site across the codebase
- Multi-provider architectures - Platform teams register different providers per domain (e.g. one for a legacy system, one for a new rollout) so multiple flag backends coexist in a single Python process
- Progressive rollouts with experimentation - Product teams pair flag evaluation with the tracking API to associate user actions with the flag variant they saw, feeding A/B test analysis
- Request-scoped targeting in web apps - Flask/FastAPI services use the transaction context propagator to attach per-request data (user ID, IP) to every flag evaluation within that request without threading it through function signatures
Under The Hood
Architecture
The SDK is organized as a thin, well-separated API layer: openfeature/api.py exposes module-level functions (get_client, set_provider, add_handler, shutdown) that delegate to a ProviderRegistry singleton (openfeature/provider/_registry.py) responsible for provider lifecycle, domain binding, and readiness state under a threading lock. OpenFeatureClient (openfeature/client.py) is the evaluation engine: each public get_*_value/get_*_details method (and its _async twin) funnels into a single generic evaluate_flag_details/evaluate_flag_details_async that merges evaluation context (global → transaction → client → invocation, per the OpenFeature spec’s precedence rules), runs before/after/error/finally hooks in the mandated ordering, dispatches to the active provider’s resolve_*_details method via a type-keyed callable map, and normalizes provider errors into FlagEvaluationDetails. Swapping the core abstraction (FeatureProvider) is the SDK’s entire reason to exist — every provider in the OpenFeature ecosystem implements that one typing.Protocol, so nothing else in application code needs to change when the backend changes.
Tech Stack
Pure-Python, dependency-free at runtime (dependencies = [] in pyproject.toml) — the SDK deliberately ships no third-party runtime deps so it can’t create version conflicts in consuming applications. Built and packaged with uv_build, targeting Python 3.10 through 3.14. Dev tooling runs entirely through uv and poethepoet task runner: ruff for linting (a large explicit rule set including bugbear, security, and pyupgrade categories) and formatting, mypy in strict mode with the Rust-based native parser, pytest/pytest-asyncio for unit tests, behave for Gherkin-driven end-to-end tests pulled from the OpenFeature spec’s own conformance suite (via a git submodule), and coverage reporting uploaded to Codecov in CI.
Code Quality
The test suite spans 35 files covering the client, API surface, hooks, providers (including a dedicated provider-compatibility suite), evaluation/transaction context, eventing, tracking, and telemetry, plus a tests/typechecking directory that exercises the public types under mypy directly. CI (.github/workflows/build.yml) runs the full suite across five Python versions on every push and PR, then separately runs the spec’s own Gherkin E2E scenarios via behave, giving strong confidence that the implementation actually matches the OpenFeature specification rather than just its own unit tests. Naming and typing are consistent throughout (extensive from __future__ import annotations, dataclasses, Protocol-based interfaces), and mypy strict mode with disallow_any_generics relaxed only where genuinely needed keeps the public API honestly typed.
API Design
The public API favors small, memorable entry points (api.set_provider, api.get_client, client.get_boolean_value) with a strict, predictable naming convention — every evaluation method has a _value (plain result) and _details (result plus reason/error/metadata) form, and every sync method has an _async counterpart with an identical signature, so developers only need to learn the pattern once. Boilerplate to get started is minimal: three lines (import, set_provider, get_client) are enough to evaluate a flag against the bundled InMemoryProvider, and the README documents every feature with a runnable snippet rather than prose alone.