diff --git a/src/google/adk/utils/feature_decorator.py b/src/google/adk/utils/feature_decorator.py index eb498724..67b5443a 100644 --- a/src/google/adk/utils/feature_decorator.py +++ b/src/google/adk/utils/feature_decorator.py @@ -26,6 +26,13 @@ import warnings T = TypeVar("T", bound=Union[Callable, type]) +def _is_truthy_env(var_name: str) -> bool: + value = os.environ.get(var_name) + if value is None: + return False + return value.strip().lower() in ("1", "true", "yes", "on") + + def _make_feature_decorator( *, label: str, @@ -66,9 +73,8 @@ def _create_decorator( @functools.wraps(orig_init) def new_init(self, *args, **kwargs): # Check if usage should be bypassed via environment variable at call time - should_bypass = ( - bypass_env_var is not None - and os.environ.get(bypass_env_var, "").lower() == "true" + should_bypass = bypass_env_var is not None and _is_truthy_env( + bypass_env_var ) if should_bypass: @@ -88,9 +94,8 @@ def _create_decorator( @functools.wraps(obj) def wrapper(*args, **kwargs): # Check if usage should be bypassed via environment variable at call time - should_bypass = ( - bypass_env_var is not None - and os.environ.get(bypass_env_var, "").lower() == "true" + should_bypass = bypass_env_var is not None and _is_truthy_env( + bypass_env_var ) if should_bypass: @@ -143,6 +148,7 @@ experimental = _make_feature_decorator( " versions without notice. It may introduce breaking changes at any" " time." ), + bypass_env_var="ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", ) """Mark a class or a function as an experimental feature. diff --git a/tests/unittests/utils/test_feature_decorator.py b/tests/unittests/utils/test_feature_decorator.py index e2f16446..7b29d6db 100644 --- a/tests/unittests/utils/test_feature_decorator.py +++ b/tests/unittests/utils/test_feature_decorator.py @@ -206,8 +206,12 @@ def test_working_in_progress_loads_from_dotenv_file(): del os.environ["ADK_ALLOW_WIP_FEATURES"] -def test_experimental_function_warns(): +def test_experimental_function_warns(monkeypatch): """Test that experimental function shows warnings (unchanged behavior).""" + # Ensure environment variable is not set + monkeypatch.delenv( + "ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", raising=False + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -220,8 +224,12 @@ def test_experimental_function_warns(): assert "breaking change in the future" in str(w[0].message) -def test_experimental_class_warns(): +def test_experimental_class_warns(monkeypatch): """Test that experimental class shows warnings (unchanged behavior).""" + # Ensure environment variable is not set + monkeypatch.delenv( + "ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", raising=False + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -235,6 +243,55 @@ def test_experimental_class_warns(): assert "class may change" in str(w[0].message) +def test_experimental_function_bypassed_with_env_var(monkeypatch): + """Experimental function emits no warning when bypass env var is true.""" + true_values = ["true", "True", "TRUE", "1", "yes", "YES", "on", "ON"] + for true_val in true_values: + monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", true_val) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = experimental_fn() + assert result == "executing" + assert len(w) == 0, f"Bypass failed for env value {true_val}" + + +def test_experimental_class_bypassed_with_env_var(monkeypatch): + """Experimental class emits no warning when bypass env var is true.""" + true_values = ["true", "True", "TRUE", "1", "yes", "YES", "on", "ON"] + for true_val in true_values: + monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", true_val) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + exp_class = ExperimentalClass() + result = exp_class.run() + assert result == "running experimental" + assert len(w) == 0, f"Bypass failed for env value {true_val}" + + +def test_experimental_function_not_bypassed_for_false_env_var(monkeypatch): + """Experimental function still warns for non-true bypass env var values.""" + false_values = ["false", "False", "FALSE", "0", "", "no", "off"] + for false_val in false_values: + monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", false_val) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + experimental_fn() + assert len(w) == 1 + assert "[EXPERIMENTAL] experimental_fn:" in str(w[0].message) + + +def test_experimental_class_not_bypassed_for_false_env_var(monkeypatch): + """Experimental class still warns for non-true bypass env var values.""" + false_values = ["false", "False", "FALSE", "0", "", "no", "off"] + for false_val in false_values: + monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", false_val) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + ExperimentalClass() + assert len(w) == 1 + assert "[EXPERIMENTAL] ExperimentalClass:" in str(w[0].message) + + def test_experimental_class_no_parens_warns(): """Test that experimental class without parentheses shows default warning.""" with warnings.catch_warnings(record=True) as w: