feat: add OpenFeature Python server provider#1
Conversation
📝 WalkthroughWalkthroughThis change adds a packaged FeatBit OpenFeature provider with evaluation-context and resolution converters, typed flag evaluation, lifecycle and status handling, tracking, documentation, CI configuration, and unit plus offline integration tests. ChangesFeatBit OpenFeature provider
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant OpenFeatureClient
participant FeatBitProvider
participant EvaluationContextConverter
participant FBClient
participant OpenFeatureEvents
OpenFeatureClient->>FeatBitProvider: resolve typed flag
FeatBitProvider->>EvaluationContextConverter: convert evaluation context
EvaluationContextConverter-->>FeatBitProvider: FeatBit user
FeatBitProvider->>FBClient: variation_detail
FBClient-->>FeatBitProvider: EvalDetail and variation
FeatBitProvider-->>OpenFeatureClient: FlagResolutionDetails
FBClient->>FeatBitProvider: status or flag-change notification
FeatBitProvider->>OpenFeatureEvents: emit provider event
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Line 20: Update the actions/checkout@v4 step to set persist-credentials to
false, ensuring the workflow does not retain the GitHub token in local Git
configuration; leave the remaining checkout behavior unchanged.
- Around line 28-29: The CI dependency installation should not use the moving
`refs/pull/14/head` override. Update the `fb-python-sdk` install command in the
workflow to honor the `pyproject.toml` constraint `>=1.1.8,<2`, or replace it
with an immutable full commit pin and document the temporary override.
In `@featbit_openfeature/provider.py`:
- Around line 74-86: Register the status listeners in initialize
unconditionally, including when wait_for_OKState times out or state is OFF.
Ensure _register_listeners() executes before propagating ProviderFatalError or
ProviderNotReadyError, while preserving the existing error classification and
successful initialization behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a9e9f0cf-2f49-4ce4-b487-a53d642e563c
📒 Files selected for processing (17)
.flake8.github/workflows/ci.yml.gitignoreMANIFEST.inREADME.mdfeatbit_openfeature/__init__.pyfeatbit_openfeature/impl/__init__.pyfeatbit_openfeature/impl/context_converter.pyfeatbit_openfeature/impl/details_converter.pyfeatbit_openfeature/provider.pyfeatbit_openfeature/py.typedpyproject.tomltests/fixtures/bootstrap.jsontests/impl/test_context_converter.pytests/impl/test_details_converter.pytests/test_offline_integration.pytests/test_provider.py
| python-version: ["3.10", "3.11", "3.12"] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Disable checkout credential persistence.
PR-controlled install and test code can read the persisted GitHub token from the local Git configuration. Set persist-credentials: false; no later step requires Git authentication.
Proposed fix
- uses: actions/checkout@v4
+ with:
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - uses: actions/checkout@v4 | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 20-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml at line 20, Update the actions/checkout@v4 step to
set persist-credentials to false, ensuring the workflow does not retain the
GitHub token in local Git configuration; leave the remaining checkout behavior
unchanged.
Source: Linters/SAST tools
| python -m pip install "fb-python-sdk @ git+https://github.com/featbit/featbit-python-sdk.git@refs/pull/14/head" | ||
| python -m pip install -e . --no-deps |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow =="
if [ -f .github/workflows/ci.yml ]; then
nl -ba .github/workflows/ci.yml | sed -n '1,140p'
else
echo ".github/workflows/ci.yml not found"
fi
echo
echo "== pyproject / package metadata =="
if [ -f pyproject.toml ]; then
nl -ba pyproject.toml | sed -n '1,220p'
else
fd -a 'pyproject.toml|setup.py|setup.cfg|requirements.*' . | sed -n '1,50p'
fi
echo
echo "== references to fb-python-sdk ==
rg -n "fb-python-sdk|featbit-python-sdk|refs/pull/14/head|--no-deps" .Repository: featbit/openfeature-provider-python-server
Length of output: 235
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow =="
if [ -f .github/workflows/ci.yml ]; then
cat -n .github/workflows/ci.yml | sed -n '1,140p'
else
echo ".github/workflows/ci.yml not found"
fi
echo
echo "== pyproject / package metadata =="
if [ -f pyproject.toml ]; then
cat -n pyproject.toml | sed -n '1,220p'
else
find . -maxdepth 3 \( -name 'pyproject.toml' -o -name 'setup.py' -o -name 'setup.cfg' -o -name 'requirements*.txt' \) -print | sort
fi
echo
echo "== references to fb-python-sdk / mutable refs / no-deps =="
grep -RIn "fb-python-sdk|featbit-python-sdk|refs/pull/14/head|uses:|pip install|\\-\\-no-deps" .github pyproject.toml 2>/dev/null || true
echo
echo "== current diff/stat summary =="
git diff --stat || true
git diff -- .github/workflows/ci.yml pyproject.toml 2>/dev/null | sed -n '1,220p'Repository: featbit/openfeature-provider-python-server
Length of output: 2667
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== pip availability/version =="
python3 -c "import shutil, sys; print('pip', shutil.which('pip'), sys.executable)" || true
python -m pip --version 2>/dev/null || true
pip --version 2>/dev/null || true
echo
echo "== behavior probe: pip resolver with mutable git ref and declared range constraint =="
python3 - <<'PY'
import subprocess, tempfile, pathlib, shlex, json, sys
cmds = [
"--dry-run",
"-r",
"/dev/stdin",
]
cases = [
("declared range", "fb-python-sdk>=1.1.8,<2"),
("mutable git ref", "git+https://github.com/featbit/featbit-python-sdk.git@refs/pull/14/head"),
]
for name, req in cases:
cmdline = [sys.executable, "-m", "pip", "install"] + cmds + [req]
print(f"\nCASE {name}: {' '.join(shlex.quote(p) for p in cmdline)}")
try:
p = subprocess.run(cmdline, text=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=80)
decoded_stderr = p.stderr.decode("utf-8", "replace")
decoded_stdout = p.stdout.decode("utf-8", "replace")
print("returncode=", p.returncode)
for line in decoded_stderr.splitlines()[-40:]:
if line.strip().startswith(("Collecting", "Downloading", "Installed", "Ignoring", "Requirement already satisfied", "Resolved:")) or "error" in line.lower().encode("ascii", "ignore"):
print(line)
for line in decoded_stdout.splitlines()[-10:]:
print(line)
except Exception as e:
print("error", type(e).__name__, e)
PYRepository: featbit/openfeature-provider-python-server
Length of output: 863
🌐 Web query:
featbit Python SDK refs/pull/14/head PyPI fb-python-sdk 1.1.8
💡 Result:
As of July 24, 2026, the official PyPI package for the FeatBit Python SDK is fb-python-sdk [1][2]. The most recent version available on PyPI is 1.1.7, which was released on June 5, 2025 [1][3]. There is no official release of version 1.1.8 for the fb-python-sdk on PyPI, nor is there an open or merged pull request numbered 14 in the official featbit/featbit-python-sdk repository that references such a version [1][4][5]. Users should rely on the official releases documented in the GitHub repository and PyPI [1][4].
Citations:
- 1: https://pypi.org/project/fb-python-sdk/
- 2: https://github.com/featbit/featbit-python-sdk
- 3: https://www.piwheels.org/project/fb-python-sdk/
- 4: https://github.com/featbit/featbit-python-sdk/releases
- 5: https://github.com/featbit/featbit-python-sdk/pulls
Install an immutable fb-python-sdk requirement instead of the PR ref.
.github/workflows/ci.yml:28 points at refs/pull/14/head, while pyproject.toml declares fb-python-sdk>=1.1.8,<2. Since CI installs the editable package with --no-deps, this bypasses the declared dependency range and depends on a moving PR ref. Use the declared constraint, or pin a full commit and document the temporary override.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 28 - 29, The CI dependency
installation should not use the moving `refs/pull/14/head` override. Update the
`fb-python-sdk` install command in the workflow to honor the `pyproject.toml`
constraint `>=1.1.8,<2`, or replace it with an immutable full commit pin and
document the temporary override.
| def initialize(self, evaluation_context: EvaluationContext) -> None: | ||
| if not self._client.initialize: | ||
| ready = self._client.update_status_provider.wait_for_OKState( | ||
| self._initialization_timeout | ||
| ) | ||
| if not ready: | ||
| state = self._client.update_status_provider.current_state | ||
| message = self._state_message(state) | ||
| if state.state_type == StateType.OFF: | ||
| raise ProviderFatalError(message) | ||
| raise ProviderNotReadyError(message) | ||
|
|
||
| self._register_listeners() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Provider can get permanently stuck NOT_READY/FATAL after an initialization timeout.
_register_listeners() (line 86) only runs when initialize() doesn't raise. If wait_for_OKState times out (or the state is OFF), the method raises before reaching line 86, so no status listener is ever registered. Per the FeatBit SDK docs, the client keeps retrying in the background after a start_wait/wait_for_OKState timeout, but per the OpenFeature spec, once initialize() fails the client status stays NOT_READY/ERROR/FATAL until the provider itself emits a recovery event. Since no listener was attached, this provider can never emit that recovery event — evaluations will keep returning the default value forever, even after FeatBit successfully connects moments later.
Register the listeners unconditionally so late recovery is still observed:
🔧 Proposed fix
def initialize(self, evaluation_context: EvaluationContext) -> None:
- if not self._client.initialize:
- ready = self._client.update_status_provider.wait_for_OKState(
- self._initialization_timeout
- )
- if not ready:
- state = self._client.update_status_provider.current_state
- message = self._state_message(state)
- if state.state_type == StateType.OFF:
- raise ProviderFatalError(message)
- raise ProviderNotReadyError(message)
-
- self._register_listeners()
+ self._register_listeners()
+ if not self._client.initialize:
+ ready = self._client.update_status_provider.wait_for_OKState(
+ self._initialization_timeout
+ )
+ if not ready:
+ state = self._client.update_status_provider.current_state
+ message = self._state_message(state)
+ if state.state_type == StateType.OFF:
+ raise ProviderFatalError(message)
+ raise ProviderNotReadyError(message)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def initialize(self, evaluation_context: EvaluationContext) -> None: | |
| if not self._client.initialize: | |
| ready = self._client.update_status_provider.wait_for_OKState( | |
| self._initialization_timeout | |
| ) | |
| if not ready: | |
| state = self._client.update_status_provider.current_state | |
| message = self._state_message(state) | |
| if state.state_type == StateType.OFF: | |
| raise ProviderFatalError(message) | |
| raise ProviderNotReadyError(message) | |
| self._register_listeners() | |
| def initialize(self, evaluation_context: EvaluationContext) -> None: | |
| self._register_listeners() | |
| if not self._client.initialize: | |
| ready = self._client.update_status_provider.wait_for_OKState( | |
| self._initialization_timeout | |
| ) | |
| if not ready: | |
| state = self._client.update_status_provider.current_state | |
| message = self._state_message(state) | |
| if state.state_type == StateType.OFF: | |
| raise ProviderFatalError(message) | |
| raise ProviderNotReadyError(message) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@featbit_openfeature/provider.py` around lines 74 - 86, Register the status
listeners in initialize unconditionally, including when wait_for_OKState times
out or state is OFF. Ensure _register_listeners() executes before propagating
ProviderFatalError or ProviderNotReadyError, while preserving the existing error
classification and successful initialization behavior.
Summary
Add the standalone FeatBit OpenFeature Python server provider as a thin wrapper around the existing FeatBit Python Server SDK.
FeatureProviderand all typed resolution methodsStatus mapping
OK→ OpenFeatureREADYINTERRUPTED→ OpenFeatureSTALEOFF→ OpenFeatureFATALNOT_READYDependencies
fb-python-sdk>=1.1.8,<2openfeature-sdk>=0.10,<1The required variation-ID and thread-safe status-listener capabilities are proposed in featbit/featbit-python-sdk#14.
Validation
53 passedv2, variantvariation-v2, and provider statusREADY