Skip to content

Commit d9aa617

Browse files
authored
Python: Create an experimental class and function decorator. (#6298)
### Motivation and Context There may be classes or functions inside of SK Python that should be deemed as experimental. Currently in Python there is no out-of-the-box way to get the decorator. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description As there is a name clash with using `experimental` as the decorator name, we are introducing two decorators: one `experimental_class` and one `experimental_function`. The note that "This {function | class} is experimental and may change in the future" is included in the docstring and a bool `is_experimental` on the class is added. <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent aa98754 commit d9aa617

File tree

5 files changed

+85
-0
lines changed

5 files changed

+85
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import types
4+
from typing import Callable, Type
5+
6+
7+
def experimental_function(func: Callable) -> Callable:
8+
if isinstance(func, types.FunctionType):
9+
if func.__doc__:
10+
func.__doc__ += "\n\nNote: This function is experimental and may change in the future."
11+
else:
12+
func.__doc__ = "Note: This function is experimental and may change in the future."
13+
14+
func.is_experimental = True
15+
16+
return func
17+
18+
19+
def experimental_class(cls: Type) -> Type:
20+
if isinstance(cls, type):
21+
if cls.__doc__:
22+
cls.__doc__ += "\n\nNote: This class is experimental and may change in the future."
23+
else:
24+
cls.__doc__ = "Note: This class is experimental and may change in the future."
25+
26+
cls.is_experimental = True
27+
28+
return cls

python/tests/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ def decorated_native_function(self) -> str:
9494
return CustomPlugin
9595

9696

97+
@pytest.fixture(scope="session")
98+
def experimental_plugin_class():
99+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
100+
from semantic_kernel.utils.experimental_decorator import experimental_class
101+
102+
@experimental_class
103+
class ExperimentalPlugin:
104+
@kernel_function(name="getLightStatus")
105+
def decorated_native_function(self) -> str:
106+
return "test"
107+
108+
return ExperimentalPlugin
109+
110+
97111
@pytest.fixture(scope="session")
98112
def create_mock_function() -> Callable:
99113
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# # Copyright (c) Microsoft. All rights reserved.
2+
3+
from semantic_kernel.utils.experimental_decorator import (
4+
experimental_function,
5+
)
6+
7+
8+
@experimental_function
9+
def my_function() -> None:
10+
"""This is a sample function docstring."""
11+
pass
12+
13+
14+
@experimental_function
15+
def my_function_no_doc_string() -> None:
16+
pass
17+
18+
19+
def test_function_experimental_decorator() -> None:
20+
assert (
21+
my_function.__doc__
22+
== "This is a sample function docstring.\n\nNote: This function is experimental and may change in the future."
23+
) # noqa: E501
24+
assert hasattr(my_function, "is_experimental")
25+
assert my_function.is_experimental is True
26+
27+
28+
def test_function_experimental_decorator_with_no_doc_string() -> None:
29+
assert my_function_no_doc_string.__doc__ == "Note: This function is experimental and may change in the future."
30+
assert hasattr(my_function_no_doc_string, "is_experimental")
31+
assert my_function_no_doc_string.is_experimental is True

python/tests/unit/functions/test_kernel_function_decorators.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
13
from typing import TYPE_CHECKING, Annotated, Any, AsyncGenerator, AsyncIterable, Optional, Union
24

35
import pytest

python/tests/unit/kernel/test_kernel.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,4 +589,14 @@ def test_instantiate_prompt_execution_settings_through_kernel(kernel_with_servic
589589
assert settings.service_id == "service"
590590

591591

592+
# endregion
593+
# experimental class decorator
594+
595+
596+
def test_experimental_class_has_decorator_and_flag(experimental_plugin_class):
597+
assert hasattr(experimental_plugin_class, "is_experimental")
598+
assert experimental_plugin_class.is_experimental
599+
assert "This class is experimental and may change in the future." in experimental_plugin_class.__doc__
600+
601+
592602
# endregion

0 commit comments

Comments
 (0)