diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 7157896d8cbccda..c193c6c447960ae 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)) @@ -2187,6 +2188,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 000000000000000..09bd5a06836df7d --- /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 c192ce5f05452fe..742a3310bc3528a 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 3d6748340930ee8..cfda2d0cd919ae5 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 @@ -8042,7 +8042,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) {