From a088e53488157143d4139487e1dfe358adbab040 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:27:56 +0300 Subject: [PATCH 01/11] Add full qualified name for abc class execption. --- Objects/typeobject.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 572e302df8d80d5..18a02a8094b727c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7340,13 +7340,20 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; } + PyObject *type_name = _PyType_GetFullyQualifiedName(type, '.'); + if (type_name == NULL) { + Py_DECREF(joined); + return NULL; + } + PyErr_Format(PyExc_TypeError, - "Can't instantiate abstract class %s " + "Can't instantiate abstract class %U " "without an implementation for abstract method%s '%U'", - type->tp_name, + type_name, method_count > 1 ? "s" : "", joined); Py_DECREF(joined); + Py_DECREF(type_name); return NULL; } PyObject *obj = type->tp_alloc(type, 0); From ef073f9c1a406fc7c8ab57b2a8f3fb43df7a2608 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:28:13 +0300 Subject: [PATCH 02/11] Fix and add more tests. --- Lib/test/test_abc.py | 64 ++++++++++++++++++++++++++++++++++-------- Lib/test/test_embed.py | 2 +- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 814d7fff2f41351..b36291eb14e33a2 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -184,7 +184,7 @@ class C(metaclass=abc_ABCMeta): @abc.abstractmethod def method_one(self): pass - msg = r"class C without an implementation for abstract method 'method_one'" + msg = r"class .*\.C without an implementation for abstract method 'method_one'" self.assertRaisesRegex(TypeError, msg, C) def test_object_new_with_many_abstractmethods(self): @@ -195,7 +195,7 @@ def method_one(self): @abc.abstractmethod def method_two(self): pass - msg = r"class C without an implementation for abstract methods 'method_one', 'method_two'" + msg = r"class .*\.C without an implementation for abstract methods 'method_one', 'method_two'" self.assertRaisesRegex(TypeError, msg, C) @warnings_helper.ignore_warnings(category=DeprecationWarning) @@ -586,7 +586,7 @@ def updated_foo(self): A.foo = updated_foo abc.update_abstractmethods(A) self.assertEqual(A.__abstractmethods__, {'foo', 'bar'}) - msg = "class A without an implementation for abstract methods 'bar', 'foo'" + msg = r"class .*\.A without an implementation for abstract methods 'bar', 'foo'" self.assertRaisesRegex(TypeError, msg, A) def test_update_implementation(self): @@ -598,7 +598,7 @@ def foo(self): class B(A): pass - msg = "class B without an implementation for abstract method 'foo'" + msg = r"class .*\.B without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, B) self.assertEqual(B.__abstractmethods__, {'foo'}) @@ -656,7 +656,7 @@ def foo(self): abc.update_abstractmethods(B) - msg = "class B without an implementation for abstract method 'foo'" + msg = r"class .*\.B without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, B) def test_update_layered_implementation(self): @@ -678,7 +678,7 @@ def foo(self): abc.update_abstractmethods(C) - msg = "class C without an implementation for abstract method 'foo'" + msg = r"class .*\.C without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, C) def test_update_multi_inheritance(self): @@ -732,18 +732,60 @@ class B(A, metaclass=abc_ABCMeta, name="test"): pass self.assertEqual(saved_kwargs, dict(name="test")) - return TestLegacyAPI, TestABC, TestABCWithInitSubclass -TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py = test_factory(_py_abc.ABCMeta, - _py_abc.get_cache_token) -TestLegacyAPI_C, TestABC_C, TestABCWithInitSubclass_C = test_factory(abc.ABCMeta, - abc.get_cache_token) + class TestAbstractClassErrorMessage(unittest.TestCase): + + class MyAbstractClass(abc.ABC): + @abc.abstractmethod + def my_method(self): + pass + + def test_error_contains_class_name(self): + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + self.assertIn("MyAbstractClass", str(cm.exception)) + + def test_error_contains_module_when_not_main(self): + original_module = self.MyAbstractClass.__module__ + + try: + self.MyAbstractClass.__module__ = "test_module" + + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + + print(str(cm.exception)) + self.assertRegex(str(cm.exception), r"test_module\..*\.MyAbstractClass") + finally: + self.MyAbstractClass.__module__ = original_module + + def test_error_without_module_when_main(self): + original_module = self.MyAbstractClass.__module__ + + try: + self.MyAbstractClass.__module__ = "__main__" + + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + + self.assertRegex(str(cm.exception), r"MyAbstractClass") + self.assertNotRegex(str(cm.exception), r"__main__\.") + finally: + self.MyAbstractClass.__module__ = original_module + + return TestLegacyAPI, TestABC, TestABCWithInitSubclass, TestAbstractClassErrorMessage + +TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py, TestAbstractClassErrorMessage_Py = test_factory(_py_abc.ABCMeta, + _py_abc.get_cache_token) +TestLegacyAPI_C, TestABC_C, TestABCWithInitSubclass_C, TestAbstractClassErrorMessage_C = test_factory(abc.ABCMeta, + abc.get_cache_token) # gh-130095: The _py_abc tests are not thread-safe when run with # `--parallel-threads` TestLegacyAPI_Py.__unittest_thread_unsafe__ = True TestABC_Py.__unittest_thread_unsafe__ = True TestABCWithInitSubclass_Py.__unittest_thread_unsafe__ = True +TestAbstractClassErrorMessage_Py.__unittest_thread_unsafe__ = True if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 1ff600e30bf4cbd..3706ecf84ae2506 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -599,7 +599,7 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str: def check_program_exitcode(self, *args, check_stderr=True, **kwargs): out, err = self.run_embedded_interpreter(*args, **kwargs) - self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123') + self.assertIn('ok! Py_RunMain() returned 123', out.rstrip()) if check_stderr: self.assertEqual(err, '') From 6a55e29d484ee5cd4234a5cb92744702f1e83d0f Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:28:25 +0300 Subject: [PATCH 03/11] Fix tests. --- Lib/test/test_dataclasses/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index a89999bb97938c0..4e8e7df17f8c677 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5066,7 +5066,7 @@ class Date(A): day: 'int' self.assertTrue(inspect.isabstract(Date)) - msg = "class Date without an implementation for abstract method 'foo'" + msg = r"class .*\.Date without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, Date) From 2d43e72d01b2aabba3ab5387c7c1fc1672f10d53 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:44:26 +0000 Subject: [PATCH 04/11] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst new file mode 100644 index 000000000000000..5b7e3ed42c67d90 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst @@ -0,0 +1 @@ +Add fully qualified name for abc class initialization exception. From c0e6f78224e409c4782b4f6a23c6d8c3281c6bf7 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Mon, 31 Aug 2026 23:34:49 +0300 Subject: [PATCH 05/11] Improve news message. --- .../2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst index 5b7e3ed42c67d90..7b0f508105d5b76 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst @@ -1 +1 @@ -Add fully qualified name for abc class initialization exception. +The :exc:`TypeError` raised when instantiating an abstract class with unimplemented abstract methods now includes the fully qualified name of the class. From 1ad563042e6c76546df9c834e466fa98e825b5b7 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Mon, 31 Aug 2026 23:44:10 +0300 Subject: [PATCH 06/11] Improve detailed text message. --- Objects/typeobject.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 18a02a8094b727c..cd3b307716e83bc 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7340,20 +7340,13 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; } - PyObject *type_name = _PyType_GetFullyQualifiedName(type, '.'); - if (type_name == NULL) { - Py_DECREF(joined); - return NULL; - } - PyErr_Format(PyExc_TypeError, - "Can't instantiate abstract class %U " + "Can't instantiate abstract class %N " "without an implementation for abstract method%s '%U'", - type_name, + type, method_count > 1 ? "s" : "", joined); Py_DECREF(joined); - Py_DECREF(type_name); return NULL; } PyObject *obj = type->tp_alloc(type, 0); From d4047874e478d0cb437ae392efbdf4ecf8bcc70b Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Mon, 31 Aug 2026 23:55:25 +0300 Subject: [PATCH 07/11] Revert embed test fix. --- Lib/test/test_embed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 3706ecf84ae2506..1ff600e30bf4cbd 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -599,7 +599,7 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str: def check_program_exitcode(self, *args, check_stderr=True, **kwargs): out, err = self.run_embedded_interpreter(*args, **kwargs) - self.assertIn('ok! Py_RunMain() returned 123', out.rstrip()) + self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123') if check_stderr: self.assertEqual(err, '') From cf11484739926293b71dba55761ab95baf98be83 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Tue, 1 Sep 2026 01:11:45 +0300 Subject: [PATCH 08/11] Remove debug print. --- Lib/test/test_abc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index b36291eb14e33a2..525f38aae2f6bce 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -754,7 +754,6 @@ def test_error_contains_module_when_not_main(self): with self.assertRaises(TypeError) as cm: self.MyAbstractClass() - print(str(cm.exception)) self.assertRegex(str(cm.exception), r"test_module\..*\.MyAbstractClass") finally: self.MyAbstractClass.__module__ = original_module From fdb4b6b20df2f723399683779df8c1ac5a8a47ce Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Tue, 1 Sep 2026 01:18:36 +0300 Subject: [PATCH 09/11] Fix class definition. --- Lib/test/test_abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 525f38aae2f6bce..6e158648fb23b66 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -735,7 +735,7 @@ class B(A, metaclass=abc_ABCMeta, name="test"): class TestAbstractClassErrorMessage(unittest.TestCase): - class MyAbstractClass(abc.ABC): + class MyAbstractClass(metaclass=abc_ABCMeta): @abc.abstractmethod def my_method(self): pass From 09ad373a06d2facbc177a2ea60ae63326bd19e7f Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Tue, 1 Sep 2026 01:18:56 +0300 Subject: [PATCH 10/11] FIx news format. --- .../2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst index 7b0f508105d5b76..dc07e228da8b1a4 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst @@ -1 +1,3 @@ -The :exc:`TypeError` raised when instantiating an abstract class with unimplemented abstract methods now includes the fully qualified name of the class. +The :exc:`TypeError` raised when instantiating an abstract class with +unimplemented abstract methods now includes the fully qualified name of the +class. \ No newline at end of file From cd64d2c1b319d8ceaec1eef300b91fd84b9a7e0b Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Tue, 1 Sep 2026 01:21:23 +0300 Subject: [PATCH 11/11] Add new line. --- .../2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst index dc07e228da8b1a4..fef286e705ca051 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst @@ -1,3 +1,3 @@ The :exc:`TypeError` raised when instantiating an abstract class with unimplemented abstract methods now includes the fully qualified name of the -class. \ No newline at end of file +class.