Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/google/adk/utils/feature_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
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,
Expand Down Expand Up @@ -66,9 +73,8 @@ def decorator(obj: T) -> T:
@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:
Expand All @@ -88,9 +94,8 @@ def new_init(self, *args, **kwargs):
@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:
Expand Down Expand Up @@ -143,6 +148,7 @@ def my_wip_function():
" 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.

Expand Down
61 changes: 59 additions & 2 deletions tests/unittests/utils/test_feature_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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")

Expand All @@ -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:
Expand Down