diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index addc905af5ede9..2f9a5dfc1a89a0 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -18,7 +18,7 @@ from functools import cached_property from test.support import MISSING_C_DOCSTRINGS -from test.support.os_helper import EnvironmentVarGuard +from test.support.os_helper import EnvironmentVarGuard, FakePath from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import TZPATH_TEST_LOCK, ZoneInfoTestBase from test.support.import_helper import import_module, CleanImport @@ -1784,6 +1784,7 @@ def test_reset_tzpath_relative_paths(self): ("/usr/share/zoneinfo", "../relative/path",), ("path/to/somewhere", "../relative/path",), ("/usr/share/zoneinfo", "path/to/somewhere", "../relative/path",), + (FakePath("path/to/somewhere"),) ] for input_paths in bad_values: with self.subTest(input_paths=input_paths): @@ -1795,6 +1796,9 @@ def test_tzpath_type_error(self): "/etc/zoneinfo:/usr/share/zoneinfo", b"/etc/zoneinfo:/usr/share/zoneinfo", 0, + (b"/bytes/path", "/valid/path"), + (FakePath(b"/bytes/path"),), + (0,), ] for bad_value in bad_values: @@ -1805,6 +1809,7 @@ def test_tzpath_type_error(self): def test_tzpath_attribute(self): tzpath_0 = [f"{DRIVE}/one", f"{DRIVE}/two"] tzpath_1 = [f"{DRIVE}/three"] + tzpath_pathlike = (FakePath(f"{DRIVE}/usr/share/zoneinfo"),) with self.tzpath_context(tzpath_0): query_0 = self.module.TZPATH @@ -1812,8 +1817,12 @@ def test_tzpath_attribute(self): with self.tzpath_context(tzpath_1): query_1 = self.module.TZPATH + with self.tzpath_context(tzpath_pathlike): + query_pathlike = self.module.TZPATH + self.assertSequenceEqual(tzpath_0, query_0) self.assertSequenceEqual(tzpath_1, query_1) + self.assertSequenceEqual(tuple([os.fspath(p) for p in tzpath_pathlike]), query_pathlike) class CTzPathTest(TzPathTest): diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 78fa6f00a8590a..177d32c35eff29 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -13,6 +13,13 @@ def _reset_tzpath(to=None, stacklevel=4): + f"not {type(tzpaths)}: {tzpaths!r}" ) + tzpaths = [os.fspath(p) for p in tzpaths] + if not all(isinstance(p, str) for p in tzpaths): + raise TypeError( + "All elements of a tzpath sequence must be strings or " + "os.PathLike objects which convert to strings." + ) + if not all(map(os.path.isabs, tzpaths)): raise ValueError(_get_invalid_paths_message(tzpaths)) base_tzpath = tzpaths diff --git a/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst new file mode 100644 index 00000000000000..b48b978a6d5c75 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst @@ -0,0 +1,6 @@ +:meth:`zoneinfo.reset_tzpath` will now convert any :class:`os.PathLike` objects +it receives into strings before adding them to ``TZPATH``. It will raise +``TypeError`` if anything other than a string is found after this conversion. +If given an :class:`os.PathLike` object that represents a relative path, it +will now raise ``ValueError`` instead of ``TypeError``, and present a more +informative error message. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst new file mode 100644 index 00000000000000..b078070166c26d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-47-23.gh-issue-116946.hj_u1t.rst @@ -0,0 +1,5 @@ +:mod:`tkinter`: the types :class:`!_tkinter.Tcl_Obj` (wrapper for Tcl objects), +:class:`!_tkinter.tktimertoken` (obtained by calling ``createtimerhandler()`` +on a :attr:`Tk ` application) and :class:`!_tkinter.tkapp` +(the runtime type of Tk applications) are now immutable. +Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst new file mode 100644 index 00000000000000..7d7d7ac5b289f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-53-19.gh-issue-116946.c-npxd.rst @@ -0,0 +1,2 @@ +:mod:`os`: the :class:`os.DirEntry` type and the type of :func:`os.scandir` +are now immutable. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst new file mode 100644 index 00000000000000..91c03fc740463f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-54-01.gh-issue-116946.hzQEWI.rst @@ -0,0 +1,3 @@ +:mod:`zlib`: the types of :func:`zlib.compressobj` +and :func:`zlib.decompressobj` are now immutable. +Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst b/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst new file mode 100644 index 00000000000000..90cf43b788137f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-14-56-40.gh-issue-116946.GGIeyO.rst @@ -0,0 +1,2 @@ +:mod:`curses.panel`: the type of :func:`curses.panel.new_panel` is now +immutable. Patch by Bénédikt Tran. diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c index 66a8c40953da8c..3b46fdf838b16f 100644 --- a/Modules/_curses_panel.c +++ b/Modules/_curses_panel.c @@ -684,6 +684,7 @@ static PyType_Spec PyCursesPanel_Type_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots = PyCursesPanel_Type_slots diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index 827dcf4dcc4c14..c306143ee73b18 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -3062,7 +3062,10 @@ static PyType_Slot RemoteUnwinder_slots[] = { static PyType_Spec RemoteUnwinder_spec = { .name = "_remote_debugging.RemoteUnwinder", .basicsize = sizeof(RemoteUnwinderObject), - .flags = Py_TPFLAGS_DEFAULT, + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_IMMUTABLETYPE + ), .slots = RemoteUnwinder_slots, }; diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index d921c46d645ac0..f0882191d3c3e8 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -906,11 +906,14 @@ static PyType_Slot PyTclObject_Type_slots[] = { }; static PyType_Spec PyTclObject_Type_spec = { - "_tkinter.Tcl_Obj", - sizeof(PyTclObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - PyTclObject_Type_slots, + .name = "_tkinter.Tcl_Obj", + .basicsize = sizeof(PyTclObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = PyTclObject_Type_slots, }; @@ -3267,11 +3270,14 @@ static PyType_Slot Tktt_Type_slots[] = { }; static PyType_Spec Tktt_Type_spec = { - "_tkinter.tktimertoken", - sizeof(TkttObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - Tktt_Type_slots, + .name = "_tkinter.tktimertoken", + .basicsize = sizeof(TkttObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = Tktt_Type_slots, }; @@ -3323,11 +3329,14 @@ static PyType_Slot Tkapp_Type_slots[] = { static PyType_Spec Tkapp_Type_spec = { - "_tkinter.tkapp", - sizeof(TkappObject), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - Tkapp_Type_slots, + .name = "_tkinter.tkapp", + .basicsize = sizeof(TkappObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = Tkapp_Type_slots, }; static PyMethodDef moduleMethods[] = diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 50d0ff1dc2127c..f229c4e8dd9322 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16050,11 +16050,14 @@ static PyType_Slot DirEntryType_slots[] = { }; static PyType_Spec DirEntryType_spec = { - MODNAME ".DirEntry", - sizeof(DirEntry), - 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, - DirEntryType_slots + .name = MODNAME ".DirEntry", + .basicsize = sizeof(DirEntry), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = DirEntryType_slots }; @@ -16492,14 +16495,17 @@ static PyType_Slot ScandirIteratorType_slots[] = { }; static PyType_Spec ScandirIteratorType_spec = { - MODNAME ".ScandirIterator", - sizeof(ScandirIterator), - 0, + .name = MODNAME ".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. - (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE - | Py_TPFLAGS_DISALLOW_INSTANTIATION), - ScandirIteratorType_slots + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_HAVE_FINALIZE + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = ScandirIteratorType_slots }; /*[clinic input] diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 0625a6f8052b6c..1ee14e31612860 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -2043,6 +2043,7 @@ static PyType_Spec Comptype_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots= Comptype_slots, @@ -2062,6 +2063,7 @@ static PyType_Spec Decomptype_spec = { .flags = ( Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC ), .slots = Decomptype_slots,