From 40ee61d8570a9a90f72b941121c062a03cccd0da Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Wed, 9 Aug 2023 13:50:13 +0300 Subject: [PATCH 1/6] gh-107805: Fix signatures of module-level generated functions in `turtle` --- Lib/test/test_turtle.py | 27 ++++++++++++ Lib/turtle.py | 41 ++++++++++--------- ...-08-09-13-49-37.gh-issue-107805.ezem0k.rst | 1 + 3 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-09-13-49-37.gh-issue-107805.ezem0k.rst diff --git a/Lib/test/test_turtle.py b/Lib/test/test_turtle.py index 3f9f129a3dd200..5df4568d44aa4b 100644 --- a/Lib/test/test_turtle.py +++ b/Lib/test/test_turtle.py @@ -461,5 +461,32 @@ def test_teleport(self): self.assertTrue(tpen.isdown()) +class TestModuleLevel(unittest.TestCase): + def test_all_signatures(self): + import inspect + import types + + known_signatures = { + 'teleport': + '(x=None, y=None, fill_gap: bool = False) -> None', + 'clear': '()', + 'reset': '(canvwidth=None, canvheight=None, bg=None)', + 'bgcolor': '(*args)', + 'pen': '(pen=None, **pendict)', + } + + for name in turtle.__all__: + obj = getattr(turtle, name) + if not isinstance(obj, types.FunctionType): + continue + + with self.subTest(name=name): + # All functions must produce correct signatures: + sig = inspect.signature(obj) + + if name in known_signatures: + self.assertEqual(str(sig), known_signatures[name]) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/turtle.py b/Lib/turtle.py index e542bc956897e9..3b1b7f330c1fd3 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -3920,28 +3920,31 @@ def getmethparlist(ob): function definition and the second is suitable for use in function call. The "self" parameter is not included. """ - defText = callText = "" + orig_sig = inspect.signature(ob) # bit of a hack for methods - turn it into a function # but we drop the "self" param. # Try and build one for Python defined functions - args, varargs, varkw = inspect.getargs(ob.__code__) - items2 = args[1:] - realArgs = args[1:] - defaults = ob.__defaults__ or [] - defaults = ["=%r" % (value,) for value in defaults] - defaults = [""] * (len(realArgs)-len(defaults)) + defaults - items1 = [arg + dflt for arg, dflt in zip(realArgs, defaults)] - if varargs is not None: - items1.append("*" + varargs) - items2.append("*" + varargs) - if varkw is not None: - items1.append("**" + varkw) - items2.append("**" + varkw) - defText = ", ".join(items1) - defText = "(%s)" % defText - callText = ", ".join(items2) - callText = "(%s)" % callText - return defText, callText + func_sig = orig_sig.replace( + parameters=list(orig_sig.parameters.values())[1:], + ) + + call_args = [] + for param in func_sig.parameters.values(): + kind = param.kind + if kind in ( + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + ): + call_args.append(param.name) + if kind == inspect.Parameter.VAR_POSITIONAL: + call_args.append(f'*{param.name}') + if kind == inspect.Parameter.KEYWORD_ONLY: + call_args.append(f'{param.name}={param.name}') + if kind == inspect.Parameter.VAR_KEYWORD: + call_args.append(f'**{param.name}') + call_text = f'({', '.join(call_args)})' + + return str(func_sig), call_text def _turtle_docrevise(docstr): """To reduce docstrings from RawTurtle class for functions diff --git a/Misc/NEWS.d/next/Library/2023-08-09-13-49-37.gh-issue-107805.ezem0k.rst b/Misc/NEWS.d/next/Library/2023-08-09-13-49-37.gh-issue-107805.ezem0k.rst new file mode 100644 index 00000000000000..263df68f8e5c80 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-09-13-49-37.gh-issue-107805.ezem0k.rst @@ -0,0 +1 @@ +Fix signatures of module-level generated functions in :mod:`turtle`. From 03ea67de37cb2b74ac26ce7a35a88951f229bc1d Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Wed, 9 Aug 2023 14:12:41 +0300 Subject: [PATCH 2/6] Fix CI --- Lib/test/test_turtle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_turtle.py b/Lib/test/test_turtle.py index 5df4568d44aa4b..4cf294056c153b 100644 --- a/Lib/test/test_turtle.py +++ b/Lib/test/test_turtle.py @@ -468,8 +468,8 @@ def test_all_signatures(self): known_signatures = { 'teleport': - '(x=None, y=None, fill_gap: bool = False) -> None', - 'clear': '()', + '(x=None, y=None, *, fill_gap: bool = False) -> None', + 'undo': '()', 'reset': '(canvwidth=None, canvheight=None, bg=None)', 'bgcolor': '(*args)', 'pen': '(pen=None, **pendict)', From 7f71ffdb415062ab8c36aa191183a891272a3f26 Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Wed, 9 Aug 2023 14:32:28 +0300 Subject: [PATCH 3/6] Fix CI --- Lib/test/test_turtle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_turtle.py b/Lib/test/test_turtle.py index 4cf294056c153b..c96341891179f9 100644 --- a/Lib/test/test_turtle.py +++ b/Lib/test/test_turtle.py @@ -470,7 +470,7 @@ def test_all_signatures(self): 'teleport': '(x=None, y=None, *, fill_gap: bool = False) -> None', 'undo': '()', - 'reset': '(canvwidth=None, canvheight=None, bg=None)', + 'goto': '(x, y=None)', 'bgcolor': '(*args)', 'pen': '(pen=None, **pendict)', } From 43f131f4310ba6b1076f8dedca80f5a357921765 Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Thu, 10 Aug 2023 09:42:14 +0300 Subject: [PATCH 4/6] Use patma --- Lib/turtle.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 3b1b7f330c1fd3..148c124f90165e 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -3930,18 +3930,20 @@ def getmethparlist(ob): call_args = [] for param in func_sig.parameters.values(): - kind = param.kind - if kind in ( - inspect.Parameter.POSITIONAL_ONLY, - inspect.Parameter.POSITIONAL_OR_KEYWORD, - ): - call_args.append(param.name) - if kind == inspect.Parameter.VAR_POSITIONAL: - call_args.append(f'*{param.name}') - if kind == inspect.Parameter.KEYWORD_ONLY: - call_args.append(f'{param.name}={param.name}') - if kind == inspect.Parameter.VAR_KEYWORD: - call_args.append(f'**{param.name}') + match param.kind: + case ( + inspect.Parameter.POSITIONAL_ONLY + | inspect.Parameter.POSITIONAL_OR_KEYWORD + ): + call_args.append(param.name) + case inspect.Parameter.VAR_POSITIONAL: + call_args.append(f'*{param.name}') + case inspect.Parameter.KEYWORD_ONLY: + call_args.append(f'{param.name}={param.name}') + case inspect.Parameter.VAR_KEYWORD: + call_args.append(f'**{param.name}') + case _: + raise RuntimeError('Unsupported parameter kind') call_text = f'({', '.join(call_args)})' return str(func_sig), call_text From 51f5bf088837e419724bfca228cd41468f7e74ad Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Thu, 10 Aug 2023 09:44:32 +0300 Subject: [PATCH 5/6] Better error message --- Lib/turtle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/turtle.py b/Lib/turtle.py index 148c124f90165e..6119b7a447e7bf 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -3943,7 +3943,7 @@ def getmethparlist(ob): case inspect.Parameter.VAR_KEYWORD: call_args.append(f'**{param.name}') case _: - raise RuntimeError('Unsupported parameter kind') + raise RuntimeError('Unsupported parameter kind', param.kind) call_text = f'({', '.join(call_args)})' return str(func_sig), call_text From 438dcbc9606d4a655e167815ea94c49f3b56c837 Mon Sep 17 00:00:00 2001 From: sobolevn <mail@sobolevn.me> Date: Mon, 21 Aug 2023 18:00:39 +0300 Subject: [PATCH 6/6] Address review --- Lib/test/test_turtle.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_turtle.py b/Lib/test/test_turtle.py index c96341891179f9..14121a590a5026 100644 --- a/Lib/test/test_turtle.py +++ b/Lib/test/test_turtle.py @@ -464,7 +464,6 @@ def test_teleport(self): class TestModuleLevel(unittest.TestCase): def test_all_signatures(self): import inspect - import types known_signatures = { 'teleport': @@ -475,17 +474,11 @@ def test_all_signatures(self): 'pen': '(pen=None, **pendict)', } - for name in turtle.__all__: - obj = getattr(turtle, name) - if not isinstance(obj, types.FunctionType): - continue - + for name in known_signatures: with self.subTest(name=name): - # All functions must produce correct signatures: + obj = getattr(turtle, name) sig = inspect.signature(obj) - - if name in known_signatures: - self.assertEqual(str(sig), known_signatures[name]) + self.assertEqual(str(sig), known_signatures[name]) if __name__ == '__main__':