DX-116029: LaunchDarkly feature flag integration#87
Merged
aniket-s-kulkarni merged 24 commits intoMar 10, 2026
Conversation
…, clean up API - Add enable_search property with LD flag override (matching allow_dml pattern) - Fix get_bool_flag/get_string_flag signatures (were passing wrong args) - Fix bare except clause in feature_flags.py - Rewrite test_launchdarkly_integration.py with proper mocked LD client tests (old tests referenced non-existent APIs: _extract_flag_metadata, set_ld_context) - Remove relay proxy references (not needed yet) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…auto prefix propagation - Introduce GetterModel base with get(field_name) pass-through - Introduce FlagAwareModel that checks LD singleton before fallback - Auto-propagate _flag_prefix via Settings.model_post_init tree walk - Remove _ld_property, raw_allow_dml/raw_enable_search, Dremio.get_flag() - Remove build_context() from FeatureFlagManager (inlined into get_flag) - Migrate all sub-models (Wlm, Metrics, HttpRetry, ApiSettings, etc.) to FlagAwareModel - Update callers (mcp.py, tools.py) to use .get() for LD-aware access - Add tests for prefix propagation, sub-model overrides, pat/enable_search regression - Add copyright header to launchdarkly_manual_test.py - All 270 tests passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…efault param to get() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…izes from settings FeatureFlagManager.instance() now lazily reads sdk_key from settings.instance().dremio.launchdarkly instead of requiring it as a parameter. This removes the eager initialization in Dremio.__init__ and simplifies the bootstrapping flow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…reModel.get() - Remove multi-context builder and unused get_bool_flag/get_string_flag from FeatureFlagManager (project_id/org_id were never passed by callers) - FeatureFlagManager.instance() now returns a disabled instance when LD is not configured instead of raising ValueError - Remove try-except ValueError block from FlagAwareModel.get() - Strip trivial docstrings from settings models and test classes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lified FeatureFlagManager - Add block comment on FlagAwareModel explaining how LD flag lookup works (prefix propagation, .get() vs direct access, model_dump unaffected) - Add log_level as top-level Settings field (default "INFO"), LD-overridable via flag key "log_level" - Settings now extends FlagAwareModel so .get() works at the top level - Update tests to match user's simplified FeatureFlagManager (raises on construction failure, no empty-string fallback) - Add TestLogLevel covering default, config, env, and LD override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…uteError for invalid fields Extract GetterMixin and FlagAwareMixin as pure mixins (no BaseModel inheritance) so they compose cleanly with both BaseModel and BaseSettings. FlagAwareModel is now just FlagAwareMixin + BaseModel for convenience. Settings uses FlagAwareMixin + BaseSettings directly, avoiding diamond inheritance. .get() now raises AttributeError if the field_name is not a valid attribute of the model, instead of silently returning None. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r.instance() - FlagAwareMixin.get() now uses _flag_prefix when calling get_flag - FeatureFlagManager.instance() gracefully handles missing LD config (catches AttributeError/TypeError, passes sdk_key=None) - Tests updated: singleton test expects disabled manager instead of exception, get_flag calls use positional args Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…etitive patterns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
launchdarkly is always present (defaulting to LaunchDarkly()), so only sdk_key can be None. This simplifies FeatureFlagManager.instance() to a single-line read without try/except. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- e2e: test allow_dml blocked without LD, allowed with LD override - e2e: test log_level override via .get() vs direct access - e2e: test LD flags don't affect config values or model_dump() - Merge test_ffm_get_flag_returns_ld_value and _not_found into parametrized test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
maxlepikhin
reviewed
Mar 10, 2026
- Add FeatureFlagManager.initialize() classmethod called by Settings.model_post_init, eliminating the need for feature_flags to import settings - Add scripts/generate_flag_keys.py to generate golden YAML of all flag keys - Add golden_flag_keys.yaml and test_flag_keys_match_golden to catch accidental flag key changes from field renames Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hints Consolidate the flag key collection logic as a utility in settings.py, replacing manual __args__/__origin__ with typing.get_args and get_type_hints. Script and test now import from settings instead of duplicating the logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dels Change OpenAi, Ollama, Anthropic, LangChain, and BeeAI from FlagAwareModel to plain BaseModel since they don't need LaunchDarkly overrides and LangChain/BeeAI are being deprecated. Prometheus already used BaseModel. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip fields whose type is a BaseModel subclass but not FlagAwareMixin (e.g. LangChain, BeeAI, LaunchDarkly, Prometheus, OAuth2) since they are opaque objects, not individual LD-toggleable values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _flag_exclude ClassVar to FlagAwareMixin; Dremio excludes uri, raw_pat, raw_project_id (connection credentials, not feature flags) - Change Tools from FlagAwareModel to plain BaseModel - get() skips LD lookup for excluded fields - collect_flag_keys() skips excluded fields - Golden keys reduced to 12 meaningful feature flags Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…el refresh - Replace _flag_exclude ClassVar with NoFlag() annotation marker on fields. Pydantic exposes Annotated metadata in field_info.metadata, so both get() and collect_flag_keys() check for NoFlag without extra introspection. - Add _start_log_level_refresh() daemon thread that syncs log level from LD flags every 60 seconds while the MCP server is running. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use FastMCP's lifespan context manager to run the periodic log level refresh as an asyncio task instead of a daemon thread, avoiding thread overhead and keeping everything in the same event loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test that the async refresh loop updates logging level when LD returns a different value, and skips set_level when the level hasn't changed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
maxlepikhin
approved these changes
Mar 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LaunchDarkly feature flag integration for runtime config overrides.
FlagAwareMixin—.get(field)checks LD before returning config value; direct attribute access andmodel_dump()unaffectedNoFlag()annotation —Annotated[type, NoFlag()]excludes fields (credentials, connection URIs) from LD lookupsFeatureFlagManager— singleton initialized bySettings.model_post_init, no cyclic imports_propagate_flag_prefixes— auto-sets LD key prefix from model nesting path (e.g.dremio.allow_dml)scripts/generate_flag_keys.pygenerates YAML of all 12 flag keys; test catches accidental renameslog_levelfrom LD every 60sDremiosubtree +log_levelare flag-aware;LangChain,BeeAI,Tools,Prometheususe plainBaseModellaunchdarklyconfig — top-level inSettings, env varDREMIOAI_LAUNCHDARKLY__SDK_KEYFlag keys
Test plan
allow_dmlandlog_leveloverrides with mocked LD client🤖 Generated with Claude Code