From 20f1fb4422606ae8b8216d1802a655716fc408c2 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Mon, 13 Jan 2025 17:41:48 +0530 Subject: [PATCH 01/21] store __module__ into a variable and restore after __new__ method creation is done --- Lib/warnings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/warnings.py b/Lib/warnings.py index e83cde37ab2d1a..deaa86b131f6de 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -585,6 +585,7 @@ def __call__(self, arg, /): import functools from types import MethodType + wrapper_module = arg.__module__ original_new = arg.__new__ @functools.wraps(original_new) @@ -600,6 +601,7 @@ def __new__(cls, *args, **kwargs): return original_new(cls) arg.__new__ = staticmethod(__new__) + arg.__module__ = wrapper_module original_init_subclass = arg.__init_subclass__ # We need slightly different behavior if __init_subclass__ From 9d0e70f78a132ee663e06ae6dfb733367c85e5ac Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Wed, 15 Jan 2025 18:29:56 +0530 Subject: [PATCH 02/21] Preserve __module__ --- Lib/warnings.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/warnings.py b/Lib/warnings.py index c5062471782010..f1d9816d2956f1 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -597,7 +597,6 @@ def __call__(self, arg, /): import functools from types import MethodType - wrapper_module = arg.__module__ original_new = arg.__new__ @functools.wraps(original_new) @@ -611,9 +610,8 @@ def __new__(cls, *args, **kwargs): raise TypeError(f"{cls.__name__}() takes no arguments") else: return original_new(cls) - + __new__.__module__ = arg.__module__ arg.__new__ = staticmethod(__new__) - arg.__module__ = wrapper_module original_init_subclass = arg.__init_subclass__ # We need slightly different behavior if __init_subclass__ @@ -637,6 +635,7 @@ def __init_subclass__(*args, **kwargs): arg.__init_subclass__ = __init_subclass__ + __init_subclass__.__module__ = arg.__module__ arg.__deprecated__ = __new__.__deprecated__ = msg __init_subclass__.__deprecated__ = msg return arg From 86924f245743b0938a1d73d5bb069c2dad190bb9 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Wed, 15 Jan 2025 19:06:44 +0530 Subject: [PATCH 03/21] Reintroduce space --- Lib/warnings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/warnings.py b/Lib/warnings.py index f1d9816d2956f1..0dc1c99ab40cc3 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -610,6 +610,7 @@ def __new__(cls, *args, **kwargs): raise TypeError(f"{cls.__name__}() takes no arguments") else: return original_new(cls) + __new__.__module__ = arg.__module__ arg.__new__ = staticmethod(__new__) From 382299e822476ed6ebc7fa847498c4f842104708 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 14:21:14 +0530 Subject: [PATCH 04/21] Add test case for help(....) function --- Lib/idlelib/idle_test/test_warning.py | 51 +++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 221068c5885fcb..36d5653a20582b 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -5,11 +5,15 @@ Revise if output destination changes (http://bugs.python.org/issue18318). Make sure warnings module is left unaltered (http://bugs.python.org/issue18081). ''' -from idlelib import run -from idlelib import pyshell as shell +import sys import unittest -from test.support import captured_stderr import warnings +from test.support import captured_stderr +from types import ModuleType + +from idlelib import pyshell as shell +from idlelib import run + # Try to capture default showwarning before Idle modules are imported. showwarning = warnings.showwarning @@ -68,6 +72,47 @@ def test_shell_show(self): 'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code') self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines()) +class TestDeprecatedHelp(unittest.TestCase): + def setUp(self): + self.module = ModuleType("testmodule") + self.code = r""" + from warnings import deprecated + + @deprecated("Test") + class A: + \"\"\"This is class A's docstring.\"\"\" + pass + """ + exec(self.code, self.module.__dict__) + sys.modules["testmodule"] = self.module + + def tearDown(self): + if "testmodule" in sys.modules: + del sys.modules["testmodule"] + + def test_help_output(self): + # Capture the help output + import io + from contextlib import redirect_stdout + + f = io.StringIO() + with redirect_stdout(f): + help(self.module) + + help_output = f.getvalue() + + self.assertIn("[DEPRECATED] Test", help_output) + self.assertIn("This is class A's docstring", help_output) + + def test_deprecation_warning(self): + # Verify the deprecation warning is raised when instantiating the class + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self.module.A() + + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertEqual(str(w[0].message), "Test") if __name__ == '__main__': unittest.main(verbosity=2) From 5b879a34bffda6e6437cacac49be0476b088b26d Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 14:40:49 +0530 Subject: [PATCH 05/21] Fix indentation --- Lib/idlelib/idle_test/test_warning.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 36d5653a20582b..2d0d9252d15f62 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -76,12 +76,12 @@ class TestDeprecatedHelp(unittest.TestCase): def setUp(self): self.module = ModuleType("testmodule") self.code = r""" - from warnings import deprecated + from warnings import deprecated - @deprecated("Test") - class A: - \"\"\"This is class A's docstring.\"\"\" - pass + @deprecated("Test") + class A: + \"\"\"This is class A's docstring.\"\"\" + pass """ exec(self.code, self.module.__dict__) sys.modules["testmodule"] = self.module From 5595a8e223bb8110086e98e4bb4f29be6ea3ffca Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 14:53:30 +0530 Subject: [PATCH 06/21] Fix test failure --- Lib/idlelib/idle_test/test_warning.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 2d0d9252d15f62..2761a695a8ae73 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -75,14 +75,12 @@ def test_shell_show(self): class TestDeprecatedHelp(unittest.TestCase): def setUp(self): self.module = ModuleType("testmodule") - self.code = r""" - from warnings import deprecated - - @deprecated("Test") - class A: - \"\"\"This is class A's docstring.\"\"\" - pass - """ + self.code = r"""\ +from warnings import deprecated +@deprecated("Test") +class A: + pass +""" exec(self.code, self.module.__dict__) sys.modules["testmodule"] = self.module @@ -101,18 +99,7 @@ def test_help_output(self): help_output = f.getvalue() - self.assertIn("[DEPRECATED] Test", help_output) - self.assertIn("This is class A's docstring", help_output) - - def test_deprecation_warning(self): - # Verify the deprecation warning is raised when instantiating the class - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.module.A() - - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, DeprecationWarning)) - self.assertEqual(str(w[0].message), "Test") + self.assertIn("Help on module testmodule:", help_output) if __name__ == '__main__': unittest.main(verbosity=2) From 4b3db9eb777ffa273c6a471bc8c989eaa019ab24 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 15:30:43 +0530 Subject: [PATCH 07/21] Update test cases --- Lib/idlelib/idle_test/test_warning.py | 44 +++++++++++++++------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 2761a695a8ae73..462ca098abc042 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -72,34 +72,40 @@ def test_shell_show(self): 'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code') self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines()) + class TestDeprecatedHelp(unittest.TestCase): - def setUp(self): - self.module = ModuleType("testmodule") - self.code = r"""\ + + def test_help_output(self): + # Capture the help output + import io + from contextlib import redirect_stdout + code = r"""\ from warnings import deprecated @deprecated("Test") class A: pass """ - exec(self.code, self.module.__dict__) - sys.modules["testmodule"] = self.module + subclass_code = r"""\ +from warnings import deprecated +@deprecated("Test") +class A: + pass - def tearDown(self): - if "testmodule" in sys.modules: +class B(A): + pass +b = B() +""" + module = ModuleType("testmodule") + for kode in (code,subclass_code): + exec(kode, module.__dict__) + sys.modules["testmodule"] = module + f = io.StringIO() + with redirect_stdout(f): + help(module) + help_output = f.getvalue() + self.assertIn("Help on module testmodule:", help_output) del sys.modules["testmodule"] - def test_help_output(self): - # Capture the help output - import io - from contextlib import redirect_stdout - - f = io.StringIO() - with redirect_stdout(f): - help(self.module) - - help_output = f.getvalue() - - self.assertIn("Help on module testmodule:", help_output) if __name__ == '__main__': unittest.main(verbosity=2) From 35731f393b30b0a9df0eacc450bac94af63ed423 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 15:32:34 +0530 Subject: [PATCH 08/21] Update tests --- Lib/idlelib/idle_test/test_warning.py | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 462ca098abc042..e0fecbe9f6b5c4 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -10,6 +10,8 @@ import warnings from test.support import captured_stderr from types import ModuleType +import io +from contextlib import redirect_stdout from idlelib import pyshell as shell from idlelib import run @@ -74,18 +76,13 @@ def test_shell_show(self): class TestDeprecatedHelp(unittest.TestCase): - - def test_help_output(self): - # Capture the help output - import io - from contextlib import redirect_stdout - code = r"""\ + CODE_SIMPLE = r""" from warnings import deprecated @deprecated("Test") class A: pass """ - subclass_code = r"""\ + CODE_SUBCLASS = r""" from warnings import deprecated @deprecated("Test") class A: @@ -95,17 +92,28 @@ class B(A): pass b = B() """ - module = ModuleType("testmodule") - for kode in (code,subclass_code): - exec(kode, module.__dict__) - sys.modules["testmodule"] = module - f = io.StringIO() - with redirect_stdout(f): - help(module) - help_output = f.getvalue() - self.assertIn("Help on module testmodule:", help_output) + def setUp(self): + self.module = ModuleType("testmodule") + + def tearDown(self): + if "testmodule" in sys.modules: del sys.modules["testmodule"] + def _get_help_output(self, code): + exec(code, self.module.__dict__) + sys.modules["testmodule"] = self.module + + f = io.StringIO() + with redirect_stdout(f): + help(self.module) + return f.getvalue() + + def test_help_output(self): + for code in (self.CODE_SIMPLE, self.CODE_SUBCLASS): + with self.subTest(code=code): + help_output = self._get_help_output(code) + self.assertIn("Help on module testmodule:", help_output) + if __name__ == '__main__': unittest.main(verbosity=2) From eb5cb3b22fe630030fc502fbb2033865f9b5c169 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 15:34:54 +0530 Subject: [PATCH 09/21] order imports --- Lib/idlelib/idle_test/test_warning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index e0fecbe9f6b5c4..ca14b7d392f457 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -5,13 +5,13 @@ Revise if output destination changes (http://bugs.python.org/issue18318). Make sure warnings module is left unaltered (http://bugs.python.org/issue18081). ''' +import io import sys import unittest import warnings +from contextlib import redirect_stdout from test.support import captured_stderr from types import ModuleType -import io -from contextlib import redirect_stdout from idlelib import pyshell as shell from idlelib import run From 7be5d8aedd513192e807bfceaacb7e54ebaacbfe Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 16:12:32 +0530 Subject: [PATCH 10/21] Improve test cases --- Lib/idlelib/idle_test/test_warning.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index ca14b7d392f457..bda3b7ec7877f5 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -81,6 +81,7 @@ class TestDeprecatedHelp(unittest.TestCase): @deprecated("Test") class A: pass +a = A() """ CODE_SUBCLASS = r""" from warnings import deprecated @@ -100,12 +101,15 @@ def tearDown(self): del sys.modules["testmodule"] def _get_help_output(self, code): - exec(code, self.module.__dict__) - sys.modules["testmodule"] = self.module + with self.assertWarns(DeprecationWarning) as cm: + exec(code, self.module.__dict__) + sys.modules["testmodule"] = self.module - f = io.StringIO() - with redirect_stdout(f): - help(self.module) + f = io.StringIO() + with redirect_stdout(f): + help(self.module) + + self.assertEqual(str(cm.warning), "Test") return f.getvalue() def test_help_output(self): From 4752d38fbd86c8565424bf1f7bb7163043875cf8 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 16 Jan 2025 17:48:08 +0530 Subject: [PATCH 11/21] Improve test case --- Lib/idlelib/idle_test/test_warning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index bda3b7ec7877f5..052e93568f1d65 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -87,8 +87,8 @@ class A: from warnings import deprecated @deprecated("Test") class A: - pass - + def __init_subclass__(self, **kwargs): + pass class B(A): pass b = B() From 8b5498267df6f13cfd13d68666a20da7cf5beac4 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Fri, 17 Jan 2025 11:50:33 +0530 Subject: [PATCH 12/21] Revert the code since it is in wrong place --- Lib/idlelib/idle_test/test_warning.py | 56 ++------------------------- 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 052e93568f1d65..221068c5885fcb 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -5,17 +5,11 @@ Revise if output destination changes (http://bugs.python.org/issue18318). Make sure warnings module is left unaltered (http://bugs.python.org/issue18081). ''' -import io -import sys +from idlelib import run +from idlelib import pyshell as shell import unittest -import warnings -from contextlib import redirect_stdout from test.support import captured_stderr -from types import ModuleType - -from idlelib import pyshell as shell -from idlelib import run - +import warnings # Try to capture default showwarning before Idle modules are imported. showwarning = warnings.showwarning @@ -75,49 +69,5 @@ def test_shell_show(self): self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines()) -class TestDeprecatedHelp(unittest.TestCase): - CODE_SIMPLE = r""" -from warnings import deprecated -@deprecated("Test") -class A: - pass -a = A() -""" - CODE_SUBCLASS = r""" -from warnings import deprecated -@deprecated("Test") -class A: - def __init_subclass__(self, **kwargs): - pass -class B(A): - pass -b = B() -""" - def setUp(self): - self.module = ModuleType("testmodule") - - def tearDown(self): - if "testmodule" in sys.modules: - del sys.modules["testmodule"] - - def _get_help_output(self, code): - with self.assertWarns(DeprecationWarning) as cm: - exec(code, self.module.__dict__) - sys.modules["testmodule"] = self.module - - f = io.StringIO() - with redirect_stdout(f): - help(self.module) - - self.assertEqual(str(cm.warning), "Test") - return f.getvalue() - - def test_help_output(self): - for code in (self.CODE_SIMPLE, self.CODE_SUBCLASS): - with self.subTest(code=code): - help_output = self._get_help_output(code) - self.assertIn("Help on module testmodule:", help_output) - - if __name__ == '__main__': unittest.main(verbosity=2) From 3c2c9d3b8b87c7ec9058d9fb7957184513060a76 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Fri, 17 Jan 2025 11:59:43 +0530 Subject: [PATCH 13/21] Move the test case to here from Lib/idlelib/idle_test/test_warning.py --- Lib/test/test_warnings/__init__.py | 46 +++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 4bd164b8a9a82b..2d008b66aebcf1 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1,4 +1,4 @@ -from contextlib import contextmanager +from contextlib import contextmanager, redirect_stdout import linecache import os import importlib @@ -1821,6 +1821,50 @@ async def coro(self): self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) + +class TestDeprecatedHelp(unittest.TestCase): + CODE_SIMPLE = r""" +from warnings import deprecated +@deprecated("Test") +class A: + pass +a = A() +""" + CODE_SUBCLASS = r""" +from warnings import deprecated +@deprecated("Test") +class A: + def __init_subclass__(self, **kwargs): + pass +class B(A): + pass +b = B() +""" + def setUp(self): + self.module = types.ModuleType("testmodule") + + def tearDown(self): + if "testmodule" in sys.modules: + del sys.modules["testmodule"] + + def _get_help_output(self, code): + with self.assertWarns(DeprecationWarning) as cm: + exec(code, self.module.__dict__) + sys.modules["testmodule"] = self.module + + f = StringIO() + with redirect_stdout(f): + help(self.module) + + self.assertEqual(str(cm.warning), "Test") + return f.getvalue() + + def test_help_output(self): + for code in (self.CODE_SIMPLE, self.CODE_SUBCLASS): + with self.subTest(code=code): + help_output = self._get_help_output(code) + self.assertIn("Help on module testmodule:", help_output) + def setUpModule(): py_warnings.onceregistry.clear() c_warnings.onceregistry.clear() From d48801e344ec818d165c81043693f281b6e2c5fa Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Fri, 17 Jan 2025 13:38:40 +0530 Subject: [PATCH 14/21] Add blurb --- .../Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst diff --git a/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst new file mode 100644 index 00000000000000..022a6aa6d81304 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst @@ -0,0 +1,3 @@ +This commit fixes an issue where help(...) wasn’t working on classes +decorated with @deprecated("...") by ensuring deprecation warnings are +correctly raised under pydoc. From 08228dc83638ab0abda7d1b18c6f04ae2c720587 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Fri, 17 Jan 2025 13:39:59 +0530 Subject: [PATCH 15/21] Update description --- .../Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst index 022a6aa6d81304..31d588b314db28 100644 --- a/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst +++ b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst @@ -1,3 +1,2 @@ This commit fixes an issue where help(...) wasn’t working on classes -decorated with @deprecated("...") by ensuring deprecation warnings are -correctly raised under pydoc. +decorated with @deprecated("...") by preserving __module__ attribute From 57e01d81224fc92cbb755d28025cae04189727e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Srinivas=20Reddy=20Thatiparthy=20=28=E0=B0=A4=E0=B0=BE?= =?UTF-8?q?=E0=B0=9F=E0=B0=BF=E0=B0=AA=E0=B0=B0=E0=B1=8D=E0=B0=A4=E0=B0=BF?= =?UTF-8?q?=20=E0=B0=B6=E0=B1=8D=E0=B0=B0=E0=B1=80=E0=B0=A8=E0=B0=BF?= =?UTF-8?q?=E0=B0=B5=E0=B0=BE=E0=B0=B8=E0=B1=8D=20=20=E0=B0=B0=E0=B1=86?= =?UTF-8?q?=E0=B0=A1=E0=B1=8D=E0=B0=A1=E0=B0=BF=29?= Date: Tue, 21 Jan 2025 07:21:39 +0530 Subject: [PATCH 16/21] Update Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> --- .../Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst index 31d588b314db28..14c2fdf2097d7b 100644 --- a/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst +++ b/Misc/NEWS.d/next/Library/2025-01-17-13-38-12.gh-issue-128772.8yEVh2.rst @@ -1,2 +1,3 @@ -This commit fixes an issue where help(...) wasn’t working on classes -decorated with @deprecated("...") by preserving __module__ attribute +Make sure the :attr:`~method.__module__` attribute is set on :meth:`~object.__new__` +and :meth:`~object.__init_subclass__` when the :func:`warnings.deprecated` decorator +is applied on classes. From 2c0b399a7feb47b5f460f843a90da185da1ae934 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Tue, 21 Jan 2025 11:19:04 +0530 Subject: [PATCH 17/21] Address review comments --- Lib/warnings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/warnings.py b/Lib/warnings.py index 0dc1c99ab40cc3..4082cdaeded834 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -634,11 +634,9 @@ def __init_subclass__(*args, **kwargs): warn(msg, category=category, stacklevel=stacklevel + 1) return original_init_subclass(*args, **kwargs) - arg.__init_subclass__ = __init_subclass__ - - __init_subclass__.__module__ = arg.__module__ arg.__deprecated__ = __new__.__deprecated__ = msg __init_subclass__.__deprecated__ = msg + __init_subclass__.__module__ = arg.__module__ return arg elif callable(arg): import functools From 0eaefef1ce75d9ea74622c42c1f295140b6e0c9d Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Tue, 21 Jan 2025 11:22:08 +0530 Subject: [PATCH 18/21] Revert the change --- Lib/warnings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/warnings.py b/Lib/warnings.py index 4082cdaeded834..9e74c25f853bfb 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -634,6 +634,8 @@ def __init_subclass__(*args, **kwargs): warn(msg, category=category, stacklevel=stacklevel + 1) return original_init_subclass(*args, **kwargs) + arg.__init_subclass__ = __init_subclass__ + arg.__deprecated__ = __new__.__deprecated__ = msg __init_subclass__.__deprecated__ = msg __init_subclass__.__module__ = arg.__module__ From 3edb9b12890025d68dedc52af66623295516c85c Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Tue, 21 Jan 2025 19:08:44 +0530 Subject: [PATCH 19/21] Remove creating an instance B() --- Lib/test/test_warnings/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 2d008b66aebcf1..89595a6085d147 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1838,7 +1838,6 @@ def __init_subclass__(self, **kwargs): pass class B(A): pass -b = B() """ def setUp(self): self.module = types.ModuleType("testmodule") From 96bab8d1896b75a22ef714d1f5893f7d5bef64f4 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Tue, 21 Jan 2025 20:43:47 +0530 Subject: [PATCH 20/21] Address review comments. Move the deprecated class to a separate data module and simplify the test that removes the need for exec function --- Lib/test/test_warnings/__init__.py | 42 ++----------------- .../test_warnings/data/deprecated_class.py | 9 ++++ 2 files changed, 13 insertions(+), 38 deletions(-) create mode 100644 Lib/test/test_warnings/data/deprecated_class.py diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 89595a6085d147..cd90c293b3feec 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1821,48 +1821,14 @@ async def coro(self): self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) - -class TestDeprecatedHelp(unittest.TestCase): - CODE_SIMPLE = r""" -from warnings import deprecated -@deprecated("Test") -class A: - pass -a = A() -""" - CODE_SUBCLASS = r""" -from warnings import deprecated -@deprecated("Test") -class A: - def __init_subclass__(self, **kwargs): - pass -class B(A): - pass -""" - def setUp(self): - self.module = types.ModuleType("testmodule") - - def tearDown(self): - if "testmodule" in sys.modules: - del sys.modules["testmodule"] - - def _get_help_output(self, code): + def test_deprecated_class(self): + import test.test_warnings.data.deprecated_class as test_module with self.assertWarns(DeprecationWarning) as cm: - exec(code, self.module.__dict__) - sys.modules["testmodule"] = self.module - f = StringIO() with redirect_stdout(f): - help(self.module) - + help(test_module) + self.assertIn(f"Help on module {test_module.__name__}", f.getvalue()) self.assertEqual(str(cm.warning), "Test") - return f.getvalue() - - def test_help_output(self): - for code in (self.CODE_SIMPLE, self.CODE_SUBCLASS): - with self.subTest(code=code): - help_output = self._get_help_output(code) - self.assertIn("Help on module testmodule:", help_output) def setUpModule(): py_warnings.onceregistry.clear() diff --git a/Lib/test/test_warnings/data/deprecated_class.py b/Lib/test/test_warnings/data/deprecated_class.py new file mode 100644 index 00000000000000..026c5ece369d51 --- /dev/null +++ b/Lib/test/test_warnings/data/deprecated_class.py @@ -0,0 +1,9 @@ +from warnings import deprecated + +@deprecated("Test") +class A: + def __init_subclass__(self, **kwargs): + pass + +class B(A): + pass From 84175fc60702c85ca387f0aac1d6c88edd3a7d01 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Tue, 21 Jan 2025 21:04:53 +0530 Subject: [PATCH 21/21] Fix failure --- Lib/test/test_warnings/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index cd90c293b3feec..c842c2c6f262dd 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1822,10 +1822,10 @@ async def coro(self): self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) def test_deprecated_class(self): - import test.test_warnings.data.deprecated_class as test_module with self.assertWarns(DeprecationWarning) as cm: f = StringIO() with redirect_stdout(f): + import test.test_warnings.data.deprecated_class as test_module help(test_module) self.assertIn(f"Help on module {test_module.__name__}", f.getvalue()) self.assertEqual(str(cm.warning), "Test")