Skip to content

Commit a088506

Browse files
xuanyang15copybara-github
authored andcommitted
chore: Add override_feature_enabled to override the default feature enable states
Co-authored-by: Xuan Yang <xygoogle@google.com> PiperOrigin-RevId: 844905911
1 parent e8ab7da commit a088506

File tree

3 files changed

+128
-2
lines changed

3 files changed

+128
-2
lines changed

src/google/adk/features/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
from ._feature_decorator import working_in_progress
1818
from ._feature_registry import FeatureName
1919
from ._feature_registry import is_feature_enabled
20+
from ._feature_registry import override_feature_enabled
2021

2122
__all__ = [
2223
"experimental",
2324
"stable",
2425
"working_in_progress",
2526
"FeatureName",
2627
"is_feature_enabled",
28+
"override_feature_enabled",
2729
]

src/google/adk/features/_feature_registry.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ class FeatureConfig:
101101
# Track which experimental features have already warned (warn only once)
102102
_WARNED_FEATURES: set[FeatureName] = set()
103103

104+
# Programmatic overrides (highest priority, checked before env vars)
105+
_FEATURE_OVERRIDES: dict[FeatureName, bool] = {}
106+
104107

105108
def _get_feature_config(
106109
feature_name: FeatureName,
@@ -129,12 +132,45 @@ def _register_feature(
129132
_FEATURE_REGISTRY[feature_name] = config
130133

131134

135+
def override_feature_enabled(
136+
feature_name: FeatureName,
137+
enabled: bool,
138+
) -> None:
139+
"""Programmatically override a feature's enabled state.
140+
141+
This override takes highest priority, superseding environment variables
142+
and registry defaults. Use this when environment variables are not
143+
available or practical in your deployment environment.
144+
145+
Args:
146+
feature_name: The feature name to override.
147+
enabled: Whether the feature should be enabled.
148+
149+
Example:
150+
```python
151+
from google.adk.features import FeatureName, override_feature_enabled
152+
153+
# Enable a feature programmatically
154+
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
155+
```
156+
"""
157+
config = _get_feature_config(feature_name)
158+
if config is None:
159+
raise ValueError(f"Feature {feature_name} is not registered.")
160+
_FEATURE_OVERRIDES[feature_name] = enabled
161+
162+
132163
def is_feature_enabled(feature_name: FeatureName) -> bool:
133164
"""Check if a feature is enabled at runtime.
134165
135166
This function is used for runtime behavior gating within stable features.
136167
It allows you to conditionally enable new behavior based on feature flags.
137168
169+
Priority order (highest to lowest):
170+
1. Programmatic overrides (via override_feature_enabled)
171+
2. Environment variables (ADK_ENABLE_* / ADK_DISABLE_*)
172+
3. Registry defaults
173+
138174
Args:
139175
feature_name: The feature name (e.g., FeatureName.RESUMABILITY).
140176
@@ -156,7 +192,14 @@ def _execute_agent_loop():
156192
if config is None:
157193
raise ValueError(f"Feature {feature_name} is not registered.")
158194

159-
# Check environment variables first (highest priority)
195+
# Check programmatic overrides first (highest priority)
196+
if feature_name in _FEATURE_OVERRIDES:
197+
enabled = _FEATURE_OVERRIDES[feature_name]
198+
if enabled and config.stage != FeatureStage.STABLE:
199+
_emit_non_stable_warning_once(feature_name, config.stage)
200+
return enabled
201+
202+
# Check environment variables second
160203
feature_name_str = (
161204
feature_name.value
162205
if isinstance(feature_name, FeatureName)

tests/unittests/features/test_feature_registry.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
import os
1818
import warnings
1919

20+
from google.adk.features._feature_registry import _FEATURE_OVERRIDES
2021
from google.adk.features._feature_registry import _FEATURE_REGISTRY
2122
from google.adk.features._feature_registry import _get_feature_config
2223
from google.adk.features._feature_registry import _register_feature
2324
from google.adk.features._feature_registry import _WARNED_FEATURES
2425
from google.adk.features._feature_registry import FeatureConfig
2526
from google.adk.features._feature_registry import FeatureStage
2627
from google.adk.features._feature_registry import is_feature_enabled
28+
from google.adk.features._feature_registry import override_feature_enabled
2729
import pytest
2830

2931
FEATURE_CONFIG_WIP = FeatureConfig(FeatureStage.WIP, default_on=False)
@@ -38,7 +40,7 @@
3840

3941
@pytest.fixture(autouse=True)
4042
def reset_env_and_registry(monkeypatch):
41-
"""Reset environment variables and registry before each test."""
43+
"""Reset environment variables, registry and overrides before each test."""
4244
# Clean up environment variables
4345
for key in list(os.environ.keys()):
4446
if key.startswith("ADK_ENABLE_") or key.startswith("ADK_DISABLE_"):
@@ -47,11 +49,17 @@ def reset_env_and_registry(monkeypatch):
4749
# Reset warned features set
4850
_WARNED_FEATURES.clear()
4951

52+
# Reset feature overrides
53+
_FEATURE_OVERRIDES.clear()
54+
5055
yield
5156

5257
# Reset warned features set
5358
_WARNED_FEATURES.clear()
5459

60+
# Reset feature overrides
61+
_FEATURE_OVERRIDES.clear()
62+
5563

5664
class TestGetFeatureConfig:
5765
"""Tests for get_feature_config() function."""
@@ -159,3 +167,76 @@ def test_warn_once_per_feature(self, monkeypatch):
159167
assert "[EXPERIMENTAL] feature DISABLED_FEATURE is enabled." in str(
160168
w[0].message
161169
)
170+
171+
172+
class TestOverrideFeatureEnabled:
173+
"""Tests for override_feature_enabled() function."""
174+
175+
def test_override_not_in_registry_raises_value_error(self):
176+
"""Overriding features not in registry raises ValueError."""
177+
with pytest.raises(ValueError):
178+
override_feature_enabled("UNKNOWN_FEATURE", True)
179+
180+
def test_override_enables_disabled_feature(self):
181+
"""Programmatic override can enable a disabled feature."""
182+
_register_feature("OVERRIDE_TEST", FEATURE_CONFIG_EXPERIMENTAL_DISABLED)
183+
assert not is_feature_enabled("OVERRIDE_TEST")
184+
185+
override_feature_enabled("OVERRIDE_TEST", True)
186+
with warnings.catch_warnings(record=True) as w:
187+
assert is_feature_enabled("OVERRIDE_TEST")
188+
assert len(w) == 1
189+
assert "[EXPERIMENTAL] feature OVERRIDE_TEST is enabled." in str(
190+
w[0].message
191+
)
192+
193+
def test_override_disables_enabled_feature(self):
194+
"""Programmatic override can disable an enabled feature."""
195+
_register_feature("OVERRIDE_TEST", FEATURE_CONFIG_EXPERIMENTAL_ENABLED)
196+
197+
override_feature_enabled("OVERRIDE_TEST", False)
198+
with warnings.catch_warnings(record=True) as w:
199+
assert not is_feature_enabled("OVERRIDE_TEST")
200+
assert not w
201+
202+
def test_override_takes_precedence_over_env_enable(self, monkeypatch):
203+
"""Programmatic override takes precedence over ADK_ENABLE_* env var."""
204+
_register_feature("PRIORITY_TEST", FEATURE_CONFIG_EXPERIMENTAL_DISABLED)
205+
206+
# Set env var to enable
207+
monkeypatch.setenv("ADK_ENABLE_PRIORITY_TEST", "true")
208+
assert is_feature_enabled("PRIORITY_TEST")
209+
210+
# But override to disable
211+
override_feature_enabled("PRIORITY_TEST", False)
212+
213+
with warnings.catch_warnings(record=True) as w:
214+
assert not is_feature_enabled("PRIORITY_TEST")
215+
assert not w
216+
217+
def test_override_takes_precedence_over_env_disable(self, monkeypatch):
218+
"""Programmatic override takes precedence over ADK_DISABLE_* env var."""
219+
_register_feature("PRIORITY_TEST", FEATURE_CONFIG_EXPERIMENTAL_ENABLED)
220+
221+
# Set env var to disable
222+
monkeypatch.setenv("ADK_DISABLE_PRIORITY_TEST", "true")
223+
assert not is_feature_enabled("PRIORITY_TEST")
224+
225+
# But override to enable
226+
override_feature_enabled("PRIORITY_TEST", True)
227+
228+
with warnings.catch_warnings(record=True) as w:
229+
assert is_feature_enabled("PRIORITY_TEST")
230+
assert len(w) == 1
231+
assert "[EXPERIMENTAL] feature PRIORITY_TEST is enabled." in str(
232+
w[0].message
233+
)
234+
235+
def test_override_stable_feature_no_warning(self):
236+
"""Overriding stable features does not emit warnings."""
237+
_register_feature("STABLE_OVERRIDE", FEATURE_CONFIG_STABLE)
238+
239+
override_feature_enabled("STABLE_OVERRIDE", True)
240+
with warnings.catch_warnings(record=True) as w:
241+
assert is_feature_enabled("STABLE_OVERRIDE")
242+
assert not w

0 commit comments

Comments
 (0)