8000 chore: Adds decorators to mark classes or fucntions as working_in_pro… · devevignesh/adk-python@0882000 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0882000

Browse files
Jacksunweicopybara-github
authored andcommitted
chore: Adds decorators to mark classes or fucntions as working_in_progress or experimental
User will see warning if they instantiate such class or call such functions. PiperOrigin-RevId: 769413048
1 parent bf47a8b commit 0882000

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
Lines changed: 99 additions & 0 dele 8000 tions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright 2025 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+
15+
from __future__ import annotations
16+
17+
import functools
18+
from typing import Callable
19+
from typing import cast
20+
from typing import TypeVar
21+
from typing import Union
22+
import warnings
23+
24+
T = TypeVar("T", bound=Union[Callable, type])
25+
26+
27+
def _make_feature_decorator(
28+
*, label: str, default_message: str
29+
) -> Callable[[str], Callable[[T], T]]:
30+
def decorator_factory(message: str = default_message) -> Callable[[T], T]:
31+
def decorator(obj: T) -> T:
32+
obj_name = getattr(obj, "__name__", type(obj).__name__)
33+
warn_msg = f"[{label.upper()}] {obj_name}: {message}"
34+
35+
if isinstance(obj, type): # decorating a class
36+
orig_init = obj.__init__
37+
38+
@functools.wraps(orig_init)
39+
def new_init(self, *args, **kwargs):
40+
warnings.warn(warn_msg, category=UserWarning, stacklevel=2)
41+
return orig_init(self, *args, **kwargs)
42+
43+
obj.__init__ = new_init # type: ignore[attr-defined]
44+
return cast(T, obj)
45+
46+
elif callable(obj): # decorating a function or method
47+
48+
@functools.wraps(obj)
49+
def wrapper(*args, **kwargs):
50+
warnings.warn(warn_msg, category=UserWarning, stacklevel=2)
51+
return obj(*args, **kwargs)
52+
53+
return cast(T, wrapper)
54+
55+
else:
56+
raise TypeError(
57+
f"@{label} can only be applied to classes or callable objects"
58+
)
59+
60+
return decorator
61+
62+
return decorator_factory
63+
64+
65+
working_in_progress = _make_feature_decorator(
66+
label="WIP",
67+
default_message=(
68+
"This feature is a work in progress and may be incomplete or unstable."
69+
),
70+
)
71+
"""Mark a class or function as a work in progress.
72+
73+
Sample usage:
74+
75+
```
76+
@working_in_progress("This feature is not ready for production use.")
77+
def my_wip_function():
78+
pass
79+
```
80+
"""
81+
82+
experimental = _make_feature_decorator(
83+
label="EXPERIMENTAL",
84+
default_message=(
85+
"This feature is experimental and may change or be removed in future"
86+
" versions without notice. It may introduce breaking changes at any"
87+
" time."
88+
),
89+
)
90+
"""Mark a class or a function as an experimental feature.
91+
92+
Sample usage:
93+
94+
```
95+
@experimental("This API may have breaking change in the future.")
96+
class ExperimentalClass:
97+
pass
98+
```
99+
"""
Lines changed: 42 additions & 0 deletions
+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import warnings
2+
3+
from google.adk.utils.feature_decorator import experimental
4+
from google.adk.utils.feature_decorator import working_in_progress
5+
6+
7+
@working_in_progress("in complete feature, don't use yet")
8+
class IncompleteFeature:
9+
10+
def run(self):
11+
return "running"
12+
13+
14+
@experimental("api may have breaking change in the future.")
15+
def experimental_fn():
16+
return "executing"
17+
18+
19+
def test_working_in_progress_class_warns():
20+
with warnings.catch_warnings(record=True) as w:
21+
warnings.simplefilter("always")
22+
23+
feature = IncompleteFeature()
24
25+
assert feature.run() == "running"
26+
assert len(w) == 1
27+
assert issubclass(w[0].category, UserWarning)
28+
assert "[WIP] IncompleteFeature:" in str(w[0].message)
29+
assert "don't use yet" in str(w[0].message)
30+
31+
32+
def test_experimental_method_warns():
33+
with warnings.catch_warnings(record=True) as w:
34+
warnings.simplefilter("always")
35+
36+
result = experimental_fn()
37+
38+
assert result == "executing"
39+
assert len(w) == 1
40+
assert issubclass(w[0].category, UserWarning)
41+
assert "[EXPERIMENTAL] experimental_fn:" in str(w[0].message)
42+
assert "breaking change in the future" in str(w[0].message)

0 commit comments

Comments
 (0)
0