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
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ def decorator(func: Callable[..., object]) -> Callable[..., object]:
setattr(func, "__kernel_function_parameters__", annotations)

return_annotation = (
_parse_parameter("return", func_sig.return_annotation, None) if func_sig.return_annotation else {}
_parse_parameter("return", func_sig.return_annotation, Parameter.empty)
if func_sig.return_annotation
else {}
)
setattr(func, "__kernel_function_return_type__", return_annotation.get("type_", "None"))
setattr(func, "__kernel_function_return_type_object__", return_annotation.get("type_object", None))
Expand Down Expand Up @@ -120,7 +122,8 @@ def _process_signature(func_sig: Signature) -> list[dict[str, Any]]:
if arg.name == "self":
continue
annotation = arg.annotation
default = arg.default if arg.default != arg.empty else None
# Parameter.empty means "no default"; a default of None is still a default.
default = arg.default
parsed_annotation = _parse_parameter(arg.name, annotation, default)
if get_origin(annotation) is Annotated or get_origin(annotation) in {Union, types.UnionType}:
underlying_type = _get_underlying_type(annotation)
Expand All @@ -137,7 +140,7 @@ def _parse_parameter(name: str, param: Any, default: Any) -> dict[str, Any]:
logger.debug(f"Parsing param: {name}")
logger.debug(f"Parsing annotation: {param}")
ret: dict[str, Any] = {"name": name}
if default is not None:
if default is not Parameter.empty:
ret["default_value"] = default
ret["is_required"] = False
else:
Expand Down
26 changes: 26 additions & 0 deletions python/tests/unit/functions/test_kernel_function_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ def func_input_object_union(self, input: InputObject | str):
def func_no_typing(self, input):
return input

@kernel_function
def func_input_none_default(self, input=None):
return input

@kernel_function
def func_input_annotated_none_default(self, input: str = None):
return input


def test_func_name_as_name():
decorator_test = MiscClass()
Expand Down Expand Up @@ -245,6 +253,24 @@ def test_kernel_function_no_typing():
assert my_func.__kernel_function_parameters__[0]["name"] == "input"


def test_kernel_function_param_none_default():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_none_default")
assert my_func.__kernel_function_parameters__[0]["type_"] == "Any"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"


def test_kernel_function_param_annotated_none_default():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_annotated_none_default")
assert my_func.__kernel_function_parameters__[0]["type_"] == "str"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"


@pytest.mark.parametrize(
("name", "annotation", "description", "type_", "is_required"),
[
Expand Down
14 changes: 14 additions & 0 deletions python/tests/unit/functions/test_kernel_function_from_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,20 @@ def test_list_func() -> list[str]:
assert str(result) == "test1,test2"


async def test_function_invoke_with_none_default_argument(kernel: Kernel):
@kernel_function(name="greet")
def greet(name: str, punctuation=None) -> str:
return f"Hello {name}{punctuation if punctuation else '!'}"

func = KernelFunction.from_method(greet, "test")

assert func.parameters[1].name == "punctuation"
assert func.parameters[1].is_required is False

result = await kernel.invoke(function=func, arguments=KernelArguments(name="Ada"))
assert result.value == "Hello Ada!"


async def test_function_invocation_filters(kernel: Kernel):
func = KernelFunctionFromMethod(method=kernel_function(lambda input: input**2, name="square"), plugin_name="math")
kernel.add_function(plugin_name="math", function=func)
Expand Down
Loading