Skip to content

Commit b592dee

Browse files
jinnigucopybara-github
authored andcommitted
feat: honor ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS
Merge #5124 Fixes #5114 PiperOrigin-RevId: 1000995317
1 parent 2a28c3e commit b592dee

10 files changed

Lines changed: 264 additions & 8 deletions

File tree

‎src/google/adk/features/_feature_registry.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from typing import Generator
2121
import warnings
2222

23+
from ..utils.env_utils import _is_experimental_warning_suppressed
2324
from ..utils.env_utils import is_env_enabled
2425

2526

@@ -417,6 +418,12 @@ def _emit_non_stable_warning_once(
417418
feature_name: The feature name.
418419
feature_stage: The feature stage.
419420
"""
421+
if (
422+
feature_stage == FeatureStage.EXPERIMENTAL
423+
and _is_experimental_warning_suppressed()
424+
):
425+
return
426+
420427
if feature_name not in _WARNED_FEATURES:
421428
_WARNED_FEATURES.add(feature_name)
422429
full_message = (

‎src/google/adk/utils/env_utils.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
_TRUTHY_ENV_VALUES = frozenset({'1', 'true', 'yes', 'on'})
2727
_STRICT_TRUTHY_ENV_VALUES = frozenset({'1', 'true'})
28+
_EXPERIMENTAL_WARNING_SUPPRESSION_ENV_VAR = (
29+
'ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS'
30+
)
2831

2932

3033
def is_env_enabled(env_var_name: str, default: str = '0') -> bool:
@@ -93,3 +96,12 @@ def is_enterprise_mode_enabled() -> bool:
9396
)
9497
return _is_genai_env_enabled('GOOGLE_GENAI_USE_VERTEXAI')
9598
return False
99+
100+
101+
def _is_experimental_warning_suppressed() -> bool:
102+
"""Check whether experimental warnings should be suppressed.
103+
104+
Returns:
105+
True if the general experimental warning suppression env var is truthy.
106+
"""
107+
return is_env_enabled(_EXPERIMENTAL_WARNING_SUPPRESSION_ENV_VAR)

‎src/google/adk/utils/feature_decorator.py‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,16 @@ class MyFeature:
4646
from typing import TypeVar
4747
import warnings
4848

49+
from .env_utils import _is_experimental_warning_suppressed
4950
from .env_utils import is_env_enabled
5051

5152
T = TypeVar("T")
5253

54+
# Gating suppression on the label string avoids adding a new parameter to
55+
# _make_feature_decorator, which would change the public constructor signatures
56+
# of experimental/a2a_experimental and trip the Breaking Change Detector.
57+
_EXPERIMENTAL_LABEL = "EXPERIMENTAL"
58+
5359

5460
class _FeatureDecorator(Protocol):
5561
"""A feature decorator usable with or without a message argument.
@@ -125,6 +131,8 @@ def decorator_factory(message_or_obj: Any = None) -> Any:
125131
def _create_decorator(
126132
message: str, label: str, block_usage: bool, bypass_env_var: Optional[str]
127133
) -> Callable[[T], T]:
134+
is_experimental = label.upper() == _EXPERIMENTAL_LABEL
135+
128136
def decorator(obj: T) -> T:
129137
obj_name = getattr(obj, "__name__", type(obj).__name__)
130138
msg = f"[{label.upper()}] {obj_name}: {message}"
@@ -140,7 +148,11 @@ def new_init(self: Any, *args: Any, **kwargs: Any) -> Any:
140148
bypass_env_var
141149
)
142150

143-
if should_bypass:
151+
if should_bypass or (
152+
is_experimental
153+
and not block_usage
154+
and _is_experimental_warning_suppressed()
155+
):
144156
# Bypass completely - no warning, no error
145157
pass
146158
elif block_usage:
@@ -162,7 +174,11 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
162174
bypass_env_var
163175
)
164176

165-
if should_bypass:
177+
if should_bypass or (
178+
is_experimental
179+
and not block_usage
180+
and _is_experimental_warning_suppressed()
181+
):
166182
# Bypass completely - no warning, no error
167183
pass
168184
elif block_usage:
@@ -216,6 +232,10 @@ def my_wip_function():
216232
)
217233
"""Mark a class or a function as an experimental feature.
218234
235+
Set ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS=true to silence these warnings.
236+
This also covers @a2a_experimental and the feature-registry
237+
"[EXPERIMENTAL] feature X is enabled." warnings.
238+
219239
Sample usage:
220240
221241
```
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Tests for a2a_experimental decorator and warning suppression."""
15+
16+
import warnings
17+
18+
from google.adk.a2a.experimental import a2a_experimental
19+
import pytest
20+
21+
22+
@pytest.fixture(autouse=True)
23+
def clear_suppression_env_vars(monkeypatch):
24+
monkeypatch.delenv(
25+
"ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", raising=False
26+
)
27+
monkeypatch.delenv(
28+
"ADK_SUPPRESS_A2A_EXPERIMENTAL_FEATURE_WARNINGS", raising=False
29+
)
30+
31+
32+
def test_a2a_experimental_class_warns_by_default():
33+
"""Classes decorated with @a2a_experimental warn when instantiated."""
34+
35+
@a2a_experimental
36+
class A2aExperimentalClass:
37+
38+
def run(self):
39+
return "running"
40+
41+
with warnings.catch_warnings(record=True) as w:
42+
warnings.simplefilter("always")
43+
feature = A2aExperimentalClass()
44+
assert feature.run() == "running"
45+
assert len(w) == 1
46+
assert "[EXPERIMENTAL] A2aExperimentalClass:" in str(w[0].message)
47+
48+
49+
def test_a2a_experimental_function_warns_by_default():
50+
"""Functions decorated with @a2a_experimental warn when called."""
51+
52+
@a2a_experimental
53+
def a2a_experimental_function():
54+
return "running fn"
55+
56+
with warnings.catch_warnings(record=True) as w:
57+
warnings.simplefilter("always")
58+
assert a2a_experimental_function() == "running fn"
59+
assert len(w) == 1
60+
assert "[EXPERIMENTAL] a2a_experimental_function:" in str(w[0].message)
61+
62+
63+
def test_a2a_experimental_warning_suppressed_by_general_env_var(monkeypatch):
64+
"""General suppression env var silences @a2a_experimental warnings."""
65+
66+
@a2a_experimental
67+
class GeneralSuppressedA2aClass:
68+
69+
def run(self):
70+
return "running"
71+
72+
@a2a_experimental
73+
def general_suppressed_a2a_function():
74+
return "running fn"
75+
76+
monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", "yes")
77+
78+
with warnings.catch_warnings(record=True) as w:
79+
warnings.simplefilter("always")
80+
feature = GeneralSuppressedA2aClass()
81+
assert feature.run() == "running"
82+
assert general_suppressed_a2a_function() == "running fn"
83+
assert not w
84+
85+
86+
def test_a2a_experimental_warning_suppressed_by_a2a_env_var(monkeypatch):
87+
"""A2A-specific suppression env var silences @a2a_experimental warnings."""
88+
89+
@a2a_experimental
90+
class A2aSuppressedClass:
91+
92+
def run(self):
93+
return "running"
94+
95+
@a2a_experimental
96+
def a2a_suppressed_function():
97+
return "running fn"
98+
99+
monkeypatch.setenv("ADK_SUPPRESS_A2A_EXPERIMENTAL_FEATURE_WARNINGS", "on")
100+
101+
with warnings.catch_warnings(record=True) as w:
102+
warnings.simplefilter("always")
103+
feature = A2aSuppressedClass()
104+
assert feature.run() == "running"
105+
assert a2a_suppressed_function() == "running fn"
106+
assert not w

‎tests/unittests/features/test_feature_decorator.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ def reset_env_and_registry(monkeypatch):
7070
"""Reset environment variables and registry before each test."""
7171
# Clean up environment variables
7272
for key in list(os.environ.keys()):
73-
if key.startswith("ADK_ENABLE_") or key.startswith("ADK_DISABLE_"):
73+
if (
74+
key.startswith("ADK_ENABLE_")
75+
or key.startswith("ADK_DISABLE_")
76+
or key == "ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS"
77+
):
7478
monkeypatch.delenv(key, raising=False)
7579

7680
# Add an existing feature to the registry

‎tests/unittests/features/test_feature_registry.py‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def reset_env_and_registry(monkeypatch):
4343
"""Reset environment variables, registry and overrides before each test."""
4444
# Clean up environment variables
4545
for key in list(os.environ.keys()):
46-
if key.startswith("ADK_ENABLE_") or key.startswith("ADK_DISABLE_"):
46+
if (
47+
key.startswith("ADK_ENABLE_")
48+
or key.startswith("ADK_DISABLE_")
49+
or key == "ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS"
50+
):
4751
monkeypatch.delenv(key, raising=False)
4852

4953
# Reset warned features set
@@ -118,6 +122,55 @@ def test_experimental_enabled_feature(self):
118122
w[0].message
119123
)
120124

125+
def test_experimental_warning_suppressed_by_general_env_var(
126+
self, monkeypatch
127+
):
128+
"""General suppression env var silences registry experimental warnings."""
129+
_register_feature("SUPPRESSED_EXP", FEATURE_CONFIG_EXPERIMENTAL_ENABLED)
130+
monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", "yes")
131+
132+
with warnings.catch_warnings(record=True) as w:
133+
warnings.simplefilter("always")
134+
assert is_feature_enabled("SUPPRESSED_EXP")
135+
assert not w
136+
137+
def test_enable_env_warning_suppressed_by_general_env_var(self, monkeypatch):
138+
"""Suppression also applies when an experimental feature is env-enabled."""
139+
_register_feature("ENV_ENABLED_EXP", FEATURE_CONFIG_EXPERIMENTAL_DISABLED)
140+
monkeypatch.setenv("ADK_ENABLE_ENV_ENABLED_EXP", "true")
141+
monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", "on")
142+
143+
with warnings.catch_warnings(record=True) as w:
144+
warnings.simplefilter("always")
145+
assert is_feature_enabled("ENV_ENABLED_EXP")
146+
assert not w
147+
148+
def test_override_warning_suppressed_by_general_env_var(self, monkeypatch):
149+
"""Suppression also applies to programmatic overrides."""
150+
_register_feature(
151+
"OVERRIDE_ENABLED_EXP", FEATURE_CONFIG_EXPERIMENTAL_DISABLED
152+
)
153+
override_feature_enabled("OVERRIDE_ENABLED_EXP", True)
154+
monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", "1")
155+
156+
with warnings.catch_warnings(record=True) as w:
157+
warnings.simplefilter("always")
158+
assert is_feature_enabled("OVERRIDE_ENABLED_EXP")
159+
assert not w
160+
161+
def test_wip_warning_not_suppressed_by_general_env_var(self, monkeypatch):
162+
"""The experimental suppression flag must not suppress WIP warnings."""
163+
_register_feature(
164+
"SUPPRESSED_WIP", FeatureConfig(FeatureStage.WIP, default_on=True)
165+
)
166+
monkeypatch.setenv("ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", "true")
167+
168+
with warnings.catch_warnings(record=True) as w:
169+
warnings.simplefilter("always")
170+
assert is_feature_enabled("SUPPRESSED_WIP")
171+
assert len(w) == 1
172+
assert "[WIP] feature SUPPRESSED_WIP is enabled." in str(w[0].message)
173+
121174
def test_stable_feature_enabled(self):
122175
"""Stable features are enabled."""
123176
_register_feature("STABLE_FEATURE", FEATURE_CONFIG_STABLE)

‎tests/unittests/integrations/eventarc/test_eventarc_toolset.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515

1616

17+
import os
1718
import unittest
1819
from unittest import mock
1920
import warnings
@@ -71,7 +72,11 @@ async def test_close_cleans_up_clients(self, mock_client):
7172
await toolset.close()
7273
mock_client.cleanup_clients.assert_called_once()
7374

75+
@mock.patch.dict(
76+
os.environ, {"ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS": "0"}
77+
)
7478
def test_eventarc_toolset_experimental_warning(self):
79+
"""Test that EventarcToolset warns when instantiated."""
7580
_WARNED_FEATURES.clear()
7681
with warnings.catch_warnings(record=True) as w:
7782
warnings.simplefilter("always")
@@ -84,7 +89,11 @@ def test_eventarc_toolset_experimental_warning(self):
8489
any("EVENTARC_TOOLSET is enabled." in str(warn.message) for warn in w)
8590
)
8691

92+
@mock.patch.dict(
93+
os.environ, {"ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS": "0"}
94+
)
8795
def test_eventarc_tool_config_experimental_warning(self):
96+
"""Test that EventarcToolConfig warns when instantiated."""
8897
_WARNED_FEATURES.clear()
8998
with warnings.catch_warnings(record=True) as w:
9099
warnings.simplefilter("always")

‎tests/unittests/tools/spanner/test_spanner_tool_settings.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626

2727

2828
@pytest.fixture(autouse=True)
29-
def reset_warned_features():
30-
"""Reset warned features before each test."""
29+
def reset_warned_features(monkeypatch):
30+
"""Reset warned features and warning suppression before each test."""
3131
_WARNED_FEATURES.clear()
32+
monkeypatch.delenv(
33+
"ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS", raising=False
34+
)
3235

3336

3437
def common_spanner_vector_store_settings(vector_length=None):
@@ -46,6 +49,7 @@ def common_spanner_vector_store_settings(vector_length=None):
4649
def test_spanner_tool_settings_experimental_warning():
4750
"""Test SpannerToolSettings experimental warning."""
4851
with warnings.catch_warnings(record=True) as w:
52+
warnings.simplefilter("always")
4953
SpannerToolSettings()
5054
assert len(w) == 1
5155
assert "SPANNER_TOOL_SETTINGS is enabled." in str(w[0].message)

‎tests/unittests/utils/test_env_utils.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import warnings
1616

17+
from google.adk.utils.env_utils import _is_experimental_warning_suppressed
1718
from google.adk.utils.env_utils import is_enterprise_mode_enabled
1819
from google.adk.utils.env_utils import is_env_enabled
1920
import pytest
@@ -129,3 +130,9 @@ def test_is_enterprise_mode_enabled_defaults_to_false(monkeypatch):
129130
monkeypatch.delenv('GOOGLE_GENAI_USE_VERTEXAI', raising=False)
130131

131132
assert is_enterprise_mode_enabled() is False
133+
134+
135+
def test_is_experimental_warning_suppressed_checks_general_env(monkeypatch):
136+
"""General suppression env var disables experimental warnings."""
137+
monkeypatch.setenv('ADK_SUPPRESS_EXPERIMENTAL_FEATURE_WARNINGS', 'yes')
138+
assert _is_experimental_warning_suppressed()

0 commit comments

Comments
 (0)