Skip to content

Commit fd63e21

Browse files
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 <noreply@anthropic.com>
1 parent 389e00f commit fd63e21

4 files changed

Lines changed: 32 additions & 9 deletions

File tree

Lib/test/test_curses.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ def test_complexchar(self):
442442
curses.complexchar('A', curses.A_BOLD))
443443
self.assertNotEqual(curses.complexchar('A'), curses.complexchar('B'))
444444
# repr() shows only a non-default attr/pair, and is a constructor call.
445-
ns = {'_curses': sys.modules[type(cc).__module__]}
445+
modname = type(cc).__module__
446+
ns = {modname: sys.modules[modname]}
446447
self.assertNotIn('attr=', repr(curses.complexchar('z')))
447448
self.assertNotIn('pair=', repr(curses.complexchar('z')))
448449
r = repr(curses.complexchar('A', curses.A_BOLD))
@@ -2237,6 +2238,24 @@ def test_has_extended_color_support(self):
22372238
r = curses.has_extended_color_support()
22382239
self.assertIsInstance(r, bool)
22392240

2241+
def test_type_names(self):
2242+
# The curses types report their public module rather than the
2243+
# underscore extension that implements them.
2244+
for name in 'window', 'complexchar', 'complexstr', 'screen', 'error':
2245+
tp = getattr(curses, name)
2246+
self.assertEqual(tp.__module__, 'curses')
2247+
self.assertEqual(tp.__qualname__, name)
2248+
self.assertEqual(tp.__name__, name)
2249+
2250+
@requires_curses_func('panel')
2251+
def test_panel_type_names(self):
2252+
import curses.panel
2253+
for name in 'panel', 'error':
2254+
tp = getattr(curses.panel, name)
2255+
self.assertEqual(tp.__module__, 'curses.panel')
2256+
self.assertEqual(tp.__qualname__, name)
2257+
self.assertEqual(tp.__name__, name)
2258+
22402259

22412260
class TestAscii(unittest.TestCase):
22422261

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The :mod:`curses` types and exceptions now report their public module in
2+
:attr:`~type.__module__`, :func:`repr` and :func:`help` -- for example
3+
``curses.window`` instead of ``_curses.window`` and ``curses.panel.error``
4+
instead of ``_curses_panel.error``.

Modules/_curses_panel.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
687687
};
688688

689689
static PyType_Spec PyCursesPanel_Type_spec = {
690-
.name = "_curses_panel.panel",
690+
.name = "curses.panel.panel",
691691
.basicsize = sizeof(PyCursesPanelObject),
692692
.flags = (
693693
Py_TPFLAGS_DEFAULT
@@ -826,9 +826,9 @@ _curses_panel_exec(PyObject *mod)
826826
return -1;
827827
}
828828

829-
/* For exception _curses_panel.error */
829+
/* For exception curses.panel.error */
830830
state->error = PyErr_NewExceptionWithDoc(
831-
"_curses_panel.error",
831+
"curses.panel.error",
832832
"Exception raised when a curses panel library function returns an error.",
833833
NULL, NULL);
834834

Modules/_cursesmodule.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4390,7 +4390,7 @@ static PyType_Slot PyCursesComplexChar_Type_slots[] = {
43904390
};
43914391

43924392
static PyType_Spec PyCursesComplexChar_Type_spec = {
4393-
.name = "_curses.complexchar",
4393+
.name = "curses.complexchar",
43944394
.basicsize = sizeof(PyCursesComplexCharObject),
43954395
.flags = Py_TPFLAGS_DEFAULT
43964396
| Py_TPFLAGS_IMMUTABLETYPE
@@ -4415,7 +4415,7 @@ static PyType_Slot PyCursesComplexStr_Type_slots[] = {
44154415
};
44164416

44174417
static PyType_Spec PyCursesComplexStr_Type_spec = {
4418-
.name = "_curses.complexstr",
4418+
.name = "curses.complexstr",
44194419
.basicsize = offsetof(PyCursesComplexStrObject, cells),
44204420
.itemsize = sizeof(cchar_t),
44214421
.flags = Py_TPFLAGS_DEFAULT
@@ -4778,7 +4778,7 @@ static PyType_Slot PyCursesWindow_Type_slots[] = {
47784778
};
47794779

47804780
static PyType_Spec PyCursesWindow_Type_spec = {
4781-
.name = "_curses.window",
4781+
.name = "curses.window",
47824782
.basicsize = sizeof(PyCursesWindowObject),
47834783
.flags = Py_TPFLAGS_DEFAULT
47844784
| Py_TPFLAGS_DISALLOW_INSTANTIATION
@@ -4954,7 +4954,7 @@ static PyType_Slot PyCursesScreen_Type_slots[] = {
49544954
};
49554955

49564956
static PyType_Spec PyCursesScreen_Type_spec = {
4957-
.name = "_curses.screen",
4957+
.name = "curses.screen",
49584958
.basicsize = sizeof(PyCursesScreenObject),
49594959
.flags = Py_TPFLAGS_DEFAULT
49604960
| Py_TPFLAGS_DISALLOW_INSTANTIATION
@@ -8116,7 +8116,7 @@ cursesmodule_exec(PyObject *module)
81168116

81178117
/* For exception curses.error */
81188118
state->error = PyErr_NewExceptionWithDoc(
8119-
"_curses.error",
8119+
"curses.error",
81208120
"Exception raised when a curses library function returns an error.",
81218121
NULL, NULL);
81228122
if (state->error == NULL) {

0 commit comments

Comments
 (0)