diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 662b57702fe26cd..6e7201d0b056c21 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -178,15 +178,28 @@ def test_type_fqdn(self): def fqdn(obj): return (obj.__module__, obj.__qualname__) - native = os.name self.assertEqual(fqdn(os.stat_result), ("os", "stat_result")) - self.assertEqual(fqdn(os.times_result), (native, "times_result")) + self.assertEqual(fqdn(os.times_result), ("os", "times_result")) + self.assertEqual(fqdn(os.DirEntry), ("os", "DirEntry")) if hasattr(os, "statvfs_result"): self.assertEqual(fqdn(os.statvfs_result), ("os", "statvfs_result")) + self.assertEqual(fqdn(os.uname_result), ("os", "uname_result")) if hasattr(os, "sched_param"): - self.assertEqual(fqdn(os.sched_param), (native, "sched_param")) + self.assertEqual(fqdn(os.sched_param), ("os", "sched_param")) if hasattr(os, "waitid_result"): - self.assertEqual(fqdn(os.waitid_result), (native, "waitid_result")) + self.assertEqual(fqdn(os.waitid_result), ("os", "waitid_result")) + + def test_pickle_global_names(self): + # Existing pickles name these via os or the platform module (posix + # or nt); both must keep loading, as must new pickles. + for name in ('stat', 'stat_result', 'statvfs_result', 'times_result', + 'uname_result'): + with self.subTest(name): + obj = getattr(os, name) + for module in ('os', os.name): + p = f'c{module}\n{name}\n.'.encode() + self.assertIs(pickle.loads(p), obj) + self.assertIs(pickle.loads(pickle.dumps(obj)), obj) # Tests creating TESTFN diff --git a/Misc/NEWS.d/next/Library/2026-11-06-05-42-11.gh-issue-71199.qJN4cV.rst b/Misc/NEWS.d/next/Library/2026-11-06-05-42-11.gh-issue-71199.qJN4cV.rst new file mode 100644 index 000000000000000..fde88d5facc9511 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-06-05-42-11.gh-issue-71199.qJN4cV.rst @@ -0,0 +1,3 @@ +Change :mod:`os` module types such as :class:`~os.DirEntry` to report ``os`` +as their module on every platform. :ref:`Pickled ` objects +using the platform-specific qualified name, such as ``nt.times_result``, still load. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9709c4e1c9454b2..67461fdb3730860 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2561,7 +2561,7 @@ static PyStructSequence_Field waitid_result_fields[] = { }; static PyStructSequence_Desc waitid_result_desc = { - MODNAME ".waitid_result", /* name */ + "os.waitid_result", /* name */ waitid_result__doc__, /* doc */ waitid_result_fields, 5 @@ -6768,7 +6768,7 @@ or via the attributes sysname, nodename, release, version, and machine.\n\ See os.uname for more information."); static PyStructSequence_Desc uname_result_desc = { - MODNAME ".uname_result", /* name */ + "os.uname_result", /* name */ uname_result__doc__, /* doc */ uname_result_fields, 5 @@ -8818,7 +8818,7 @@ static PyStructSequence_Field sched_param_fields[] = { }; static PyStructSequence_Desc sched_param_desc = { - MODNAME ".sched_param", /* name */ + "os.sched_param", /* name */ os_sched_param__doc__, /* doc */ sched_param_fields, 1 @@ -11376,7 +11376,7 @@ and elapsed.\n\ See os.times for more information."); static PyStructSequence_Desc times_result_desc = { - MODNAME ".times_result", /* name */ + "os.times_result", /* name */ times_result__doc__, /* doc */ times_result_fields, 5 @@ -16835,7 +16835,7 @@ static PyType_Slot DirEntryType_slots[] = { }; static PyType_Spec DirEntryType_spec = { - .name = MODNAME ".DirEntry", + .name = "os.DirEntry", .basicsize = sizeof(DirEntry), .flags = ( Py_TPFLAGS_DEFAULT @@ -17392,7 +17392,7 @@ static PyType_Slot ScandirIteratorType_slots[] = { }; static PyType_Spec ScandirIteratorType_spec = { - .name = MODNAME ".ScandirIterator", + .name = "os.ScandirIterator", .basicsize = sizeof(ScandirIterator), // bpo-40549: Py_TPFLAGS_BASETYPE should not be used, since // PyType_GetModule(Py_TYPE(self)) doesn't work on a subclass instance.