From b346f561ef3830f716cf82ebe1ea1b576da9a1c1 Mon Sep 17 00:00:00 2001 From: jeanluc Date: Wed, 29 Jul 2026 10:56:38 +0200 Subject: [PATCH 1/2] Add tests for issue #69901 --- .../functional/utils/functools/test_alias_function.py | 11 +++++++++++ .../utils/functools/test_namespaced_function.py | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/pytests/functional/utils/functools/test_alias_function.py diff --git a/tests/pytests/functional/utils/functools/test_alias_function.py b/tests/pytests/functional/utils/functools/test_alias_function.py new file mode 100644 index 000000000000..18cc4ea983ed --- /dev/null +++ b/tests/pytests/functional/utils/functools/test_alias_function.py @@ -0,0 +1,11 @@ +from salt.utils.functools import alias_function + + +def test_kwarg_defaults_preserved(): + def func(_arg, *, default="foo"): + return default + + func2 = alias_function(func, "func2") + + assert func(None) == "foo" + assert func2(None) == "foo" diff --git a/tests/pytests/functional/utils/functools/test_namespaced_function.py b/tests/pytests/functional/utils/functools/test_namespaced_function.py index 283a722a87c7..9d4648035ec6 100644 --- a/tests/pytests/functional/utils/functools/test_namespaced_function.py +++ b/tests/pytests/functional/utils/functools/test_namespaced_function.py @@ -130,3 +130,13 @@ def foo(): "for removal in 3008.0 (Argon) and no longer does anything for the function " "being namespaced." ) + + +def test_kwarg_defaults_preserved(): + def func(_arg, *, default="foo"): + return default + + func2 = namespaced_function(func, globals()) + + assert func(None) == "foo" + assert func2(None) == "foo" From cfbdab892e2b2b75f283659c387164da6b3c1ae8 Mon Sep 17 00:00:00 2001 From: jeanluc Date: Wed, 29 Jul 2026 11:01:44 +0200 Subject: [PATCH 2/2] Account for kwdefaults when copying function --- changelog/69901.fixed.md | 1 + salt/utils/functools.py | 6 ++++++ .../functional/utils/functools/test_alias_function.py | 2 +- .../functional/utils/functools/test_namespaced_function.py | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 changelog/69901.fixed.md diff --git a/changelog/69901.fixed.md b/changelog/69901.fixed.md new file mode 100644 index 000000000000..c28474dd27fd --- /dev/null +++ b/changelog/69901.fixed.md @@ -0,0 +1 @@ +Fixed `salt.utils.functools` `namespaced_function`/`alias_function` dropping keyword-only argument defaults in copied function diff --git a/salt/utils/functools.py b/salt/utils/functools.py index 224d096957c4..d7dbe4b7e28b 100644 --- a/salt/utils/functools.py +++ b/salt/utils/functools.py @@ -61,6 +61,9 @@ def namespaced_function(function, global_dict, defaults=None, preserve_context=N closure=function.__closure__, ) new_namespaced_function.__dict__.update(function.__dict__) + if function.__kwdefaults__ is not None: + # Only Py 3.13+ accept this in FunctionType.__new__ + new_namespaced_function.__kwdefaults__ = function.__kwdefaults__.copy() return new_namespaced_function @@ -76,6 +79,9 @@ def alias_function(fun, name, doc=None): fun.__closure__, ) alias_fun.__dict__.update(fun.__dict__) + if fun.__kwdefaults__ is not None: + # Only Py 3.13+ accept this in FunctionType.__new__ + alias_fun.__kwdefaults__ = fun.__kwdefaults__.copy() if doc and isinstance(doc, str): alias_fun.__doc__ = doc diff --git a/tests/pytests/functional/utils/functools/test_alias_function.py b/tests/pytests/functional/utils/functools/test_alias_function.py index 18cc4ea983ed..80647f72adee 100644 --- a/tests/pytests/functional/utils/functools/test_alias_function.py +++ b/tests/pytests/functional/utils/functools/test_alias_function.py @@ -8,4 +8,4 @@ def func(_arg, *, default="foo"): func2 = alias_function(func, "func2") assert func(None) == "foo" - assert func2(None) == "foo" + assert func2(None) == "foo" # pylint: disable=not-callable diff --git a/tests/pytests/functional/utils/functools/test_namespaced_function.py b/tests/pytests/functional/utils/functools/test_namespaced_function.py index 9d4648035ec6..f423f1393250 100644 --- a/tests/pytests/functional/utils/functools/test_namespaced_function.py +++ b/tests/pytests/functional/utils/functools/test_namespaced_function.py @@ -139,4 +139,4 @@ def func(_arg, *, default="foo"): func2 = namespaced_function(func, globals()) assert func(None) == "foo" - assert func2(None) == "foo" + assert func2(None) == "foo" # pylint: disable=not-callable