https://github.com/python/cpython/commit/fd63e21c0b190812556696211ef6bbe286b92075
commit: fd63e21c0b190812556696211ef6bbe286b92075
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-27T10:10:11+03:00
summary:

gh-87904: Report the public module name in curses types and exceptions 
(GH-152341)

The curses C types and exceptions now set their tp_name to the public
module, so __module__, repr() and help() report curses.window,
curses.complexchar, curses.complexstr, curses.screen, curses.error,
curses.panel.panel and curses.panel.error instead of the underscore
extension modules.

Co-authored-by: Claude Opus 4.8 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-26-22-06-07.gh-issue-87904.F71C70.rst
M Lib/test/test_curses.py
M Modules/_curses_panel.c
M Modules/_cursesmodule.c

diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index b63fb63476f425..3df67c2bf0fafa 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -442,7 +442,8 @@ def test_complexchar(self):
                             curses.complexchar('A', curses.A_BOLD))
         self.assertNotEqual(curses.complexchar('A'), curses.complexchar('B'))
         # repr() shows only a non-default attr/pair, and is a constructor call.
-        ns = {'_curses': sys.modules[type(cc).__module__]}
+        modname = type(cc).__module__
+        ns = {modname: sys.modules[modname]}
         self.assertNotIn('attr=', repr(curses.complexchar('z')))
         self.assertNotIn('pair=', repr(curses.complexchar('z')))
         r = repr(curses.complexchar('A', curses.A_BOLD))
@@ -2237,6 +2238,24 @@ def test_has_extended_color_support(self):
         r = curses.has_extended_color_support()
         self.assertIsInstance(r, bool)
 
+    def test_type_names(self):
+        # The curses types report their public module rather than the
+        # underscore extension that implements them.
+        for name in 'window', 'complexchar', 'complexstr', 'screen', 'error':
+            tp = getattr(curses, name)
+            self.assertEqual(tp.__module__, 'curses')
+            self.assertEqual(tp.__qualname__, name)
+            self.assertEqual(tp.__name__, name)
+
+    @requires_curses_func('panel')
+    def test_panel_type_names(self):
+        import curses.panel
+        for name in 'panel', 'error':
+            tp = getattr(curses.panel, name)
+            self.assertEqual(tp.__module__, 'curses.panel')
+            self.assertEqual(tp.__qualname__, name)
+            self.assertEqual(tp.__name__, name)
+
 
 class TestAscii(unittest.TestCase):
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-26-22-06-07.gh-issue-87904.F71C70.rst 
b/Misc/NEWS.d/next/Library/2026-06-26-22-06-07.gh-issue-87904.F71C70.rst
new file mode 100644
index 00000000000000..09bd5a06836df7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-26-22-06-07.gh-issue-87904.F71C70.rst
@@ -0,0 +1,4 @@
+The :mod:`curses` types and exceptions now report their public module in
+:attr:`~type.__module__`, :func:`repr` and :func:`help` -- for example
+``curses.window`` instead of ``_curses.window`` and ``curses.panel.error``
+instead of ``_curses_panel.error``.
diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c
index c192ce5f05452f..742a3310bc3528 100644
--- a/Modules/_curses_panel.c
+++ b/Modules/_curses_panel.c
@@ -687,7 +687,7 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
 };
 
 static PyType_Spec PyCursesPanel_Type_spec = {
-    .name = "_curses_panel.panel",
+    .name = "curses.panel.panel",
     .basicsize = sizeof(PyCursesPanelObject),
     .flags = (
         Py_TPFLAGS_DEFAULT
@@ -826,9 +826,9 @@ _curses_panel_exec(PyObject *mod)
         return -1;
     }
 
-    /* For exception _curses_panel.error */
+    /* For exception curses.panel.error */
     state->error = PyErr_NewExceptionWithDoc(
-        "_curses_panel.error",
+        "curses.panel.error",
         "Exception raised when a curses panel library function returns an 
error.",
         NULL, NULL);
 
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 4d37e244eba7d2..0a77469581432f 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -4390,7 +4390,7 @@ static PyType_Slot PyCursesComplexChar_Type_slots[] = {
 };
 
 static PyType_Spec PyCursesComplexChar_Type_spec = {
-    .name = "_curses.complexchar",
+    .name = "curses.complexchar",
     .basicsize = sizeof(PyCursesComplexCharObject),
     .flags = Py_TPFLAGS_DEFAULT
         | Py_TPFLAGS_IMMUTABLETYPE
@@ -4415,7 +4415,7 @@ static PyType_Slot PyCursesComplexStr_Type_slots[] = {
 };
 
 static PyType_Spec PyCursesComplexStr_Type_spec = {
-    .name = "_curses.complexstr",
+    .name = "curses.complexstr",
     .basicsize = offsetof(PyCursesComplexStrObject, cells),
     .itemsize = sizeof(cchar_t),
     .flags = Py_TPFLAGS_DEFAULT
@@ -4778,7 +4778,7 @@ static PyType_Slot PyCursesWindow_Type_slots[] = {
 };
 
 static PyType_Spec PyCursesWindow_Type_spec = {
-    .name = "_curses.window",
+    .name = "curses.window",
     .basicsize =  sizeof(PyCursesWindowObject),
     .flags = Py_TPFLAGS_DEFAULT
         | Py_TPFLAGS_DISALLOW_INSTANTIATION
@@ -4954,7 +4954,7 @@ static PyType_Slot PyCursesScreen_Type_slots[] = {
 };
 
 static PyType_Spec PyCursesScreen_Type_spec = {
-    .name = "_curses.screen",
+    .name = "curses.screen",
     .basicsize = sizeof(PyCursesScreenObject),
     .flags = Py_TPFLAGS_DEFAULT
         | Py_TPFLAGS_DISALLOW_INSTANTIATION
@@ -8116,7 +8116,7 @@ cursesmodule_exec(PyObject *module)
 
     /* For exception curses.error */
     state->error = PyErr_NewExceptionWithDoc(
-        "_curses.error",
+        "curses.error",
         "Exception raised when a curses library function returns an error.",
         NULL, NULL);
     if (state->error == NULL) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to