diff --git a/Doc/conf.py b/Doc/conf.py index 8a14646801ebac..1d03da1dc92935 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -200,7 +200,10 @@ # Undocumented modules that users shouldn't have to worry about # (implementation details of `os.path`): ('py:mod', 'ntpath'), + ('py:mod', 'ntpath.pure'), ('py:mod', 'posixpath'), + ('py:mod', 'posixpath.pure'), + ('py:mod', 'os.path.pure'), ] # Temporary undocumented names. diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index b582321515db56..9a36e13b004c2a 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -4,8 +4,8 @@ .. module:: os.path :synopsis: Operations on pathnames. -**Source code:** :source:`Lib/genericpath.py`, :source:`Lib/posixpath.py` (for POSIX) and -:source:`Lib/ntpath.py` (for Windows). +**Source code:** :source:`Lib/genericpath.py`, :source:`Lib/posixpath/` (for POSIX) and +:source:`Lib/ntpath/` (for Windows). .. index:: single: path; operations @@ -42,8 +42,8 @@ the :mod:`glob` module.) a path that is *always* in one of the different formats. They all have the same interface: - * :mod:`posixpath` for UNIX-style paths - * :mod:`ntpath` for Windows paths + * :mod:`posixpath.pure` for UNIX-style paths + * :mod:`ntpath.pure` for Windows paths .. versionchanged:: 3.8 diff --git a/Lib/ntpath.py b/Lib/ntpath/__init__.py similarity index 100% rename from Lib/ntpath.py rename to Lib/ntpath/__init__.py diff --git a/Lib/ntpath/pure.py b/Lib/ntpath/pure.py new file mode 100644 index 00000000000000..9b8d339fd5523a --- /dev/null +++ b/Lib/ntpath/pure.py @@ -0,0 +1,22 @@ +# Module 'ntpath.pure' -- pure operations on WinNT/Win95 pathnames +"""Pure pathname manipulations, WindowsNT/95 version.""" +import os +import ntpath +from ntpath import (altsep, basename, commonpath, commonprefix, curdir, + dirname, extsep, isabs, isreserved, join, normcase, + normpath, pardir, pathsep, sep, split, splitdrive, + splitext, splitroot) + +__all__ = ["altsep", "basename", "commonpath", "commonprefix", "curdir", + "dirname", "extsep", "isabs", "isreserved", "join", "normcase", + "normpath", "pardir", "pathsep", "relpath", "sep", "split", + "splitdrive", "splitext", "splitroot"] + +def relpath(path, start): + """Return a relative version of a path""" + path = os.fspath(path) + start = os.fspath(start) + if not isabs(path) or not isabs(start): + raise ValueError("paths are not absolute") + + return ntpath.relpath(path, start) diff --git a/Lib/posixpath.py b/Lib/posixpath/__init__.py similarity index 99% rename from Lib/posixpath.py rename to Lib/posixpath/__init__.py index fccca4e066b76f..3b062c7e5c59bf 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath/__init__.py @@ -5,9 +5,6 @@ module on Posix systems; on other systems (e.g. Windows), os.path provides the same operations in a manner specific to that platform, and is an alias to another module (e.g. ntpath). - -Some of this can actually be useful on non-Posix systems too, e.g. -for manipulation of the pathname component of URLs. """ # Strings representing various path-related bits and pieces. diff --git a/Lib/posixpath/pure.py b/Lib/posixpath/pure.py new file mode 100644 index 00000000000000..72bfa3695c907f --- /dev/null +++ b/Lib/posixpath/pure.py @@ -0,0 +1,25 @@ +"""Pure operations on Posix pathnames. + +This can actually be useful on non-Posix systems too, e.g. +for manipulation of the pathname component of URLs. +""" +import os +import posixpath +from posixpath import (altsep, basename, commonpath, commonprefix, curdir, + dirname, extsep, isabs, join, normcase, normpath, + pardir, pathsep, sep, split, splitdrive, splitext, + splitroot) + +__all__ = ["altsep", "basename", "commonpath", "commonprefix", "curdir", + "dirname", "extsep", "isabs", "join", "normcase", "normpath", + "pardir", "pathsep", "relpath", "sep", "split", "splitdrive", + "splitext", "splitroot"] + +def relpath(path, start): + """Return a relative version of a path""" + path = os.fspath(path) + start = os.fspath(start) + if not isabs(path) or not isabs(start): + raise ValueError("paths are not absolute") + + return posixpath.relpath(path, start) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 64cbfaaaaa0690..11bc91ca5bef55 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1,5 +1,6 @@ import inspect import ntpath +import ntpath.pure import os import string import subprocess @@ -863,6 +864,17 @@ def test_relpath(self): tester('ntpath.relpath("/a/b", "/a/b")', '.') tester('ntpath.relpath("c:/foo", "C:/FOO")', '.') + def test_pure_relpath(self): + self.assertRaises(ValueError, ntpath.pure.relpath, 'foo', 'bar') + self.assertRaises(ValueError, ntpath.pure.relpath, 'foo', 'C:/bar') + self.assertRaises(ValueError, ntpath.pure.relpath, 'C:/foo', 'bar') + tester('ntpath.pure.relpath("C:/foo", "C:/bar")', r'..\foo') + + # test bytes + self.assertRaises(ValueError, ntpath.pure.relpath, b'foo', b'bar') + self.assertRaises(ValueError, ntpath.pure.relpath, b'foo', b'C:/bar') + self.assertRaises(ValueError, ntpath.pure.relpath, b'C:/foo', b'bar') + def test_commonpath(self): def check(paths, expected): tester(('ntpath.commonpath(%r)' % paths).replace('\\\\', '\\'), diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 57a24e9c70d5e5..74c4a12a94d408 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -1,6 +1,7 @@ import inspect import os import posixpath +import posixpath.pure import sys import unittest from posixpath import realpath, abspath, dirname, basename @@ -746,6 +747,18 @@ def test_relpath_bytes(self): finally: os.getcwdb = real_getcwdb + def test_pure_relpath(self): + self.assertRaises(ValueError, posixpath.pure.relpath, 'foo', 'bar') + self.assertRaises(ValueError, posixpath.pure.relpath, 'foo', '/bar') + self.assertRaises(ValueError, posixpath.pure.relpath, '/foo', 'bar') + self.assertEqual(posixpath.pure.relpath('/foo', '/bar'), '../foo') + + def test_pure_relpath_bytes(self): + self.assertRaises(ValueError, posixpath.pure.relpath, b'foo', b'bar') + self.assertRaises(ValueError, posixpath.pure.relpath, b'foo', b'/bar') + self.assertRaises(ValueError, posixpath.pure.relpath, b'/foo', b'bar') + self.assertEqual(posixpath.pure.relpath(b'/foo', b'/bar'), b'../foo') + def test_commonpath(self): def check(paths, expected): self.assertEqual(posixpath.commonpath(paths), expected) diff --git a/Makefile.pre.in b/Makefile.pre.in index 22dba279faa935..7d724483c54278 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1468,8 +1468,10 @@ FROZEN_FILES_IN = \ Lib/_collections_abc.py \ Lib/_sitebuiltins.py \ Lib/genericpath.py \ - Lib/ntpath.py \ - Lib/posixpath.py \ + Lib/ntpath/__init__.py \ + Lib/ntpath/pure.py \ + Lib/posixpath/__init__.py \ + Lib/posixpath/pure.py \ Lib/os.py \ Lib/site.py \ Lib/stat.py \ @@ -1494,7 +1496,9 @@ FROZEN_FILES_OUT = \ Python/frozen_modules/_sitebuiltins.h \ Python/frozen_modules/genericpath.h \ Python/frozen_modules/ntpath.h \ + Python/frozen_modules/ntpath.pure.h \ Python/frozen_modules/posixpath.h \ + Python/frozen_modules/posixpath.pure.h \ Python/frozen_modules/os.h \ Python/frozen_modules/site.h \ Python/frozen_modules/stat.h \ @@ -1549,11 +1553,17 @@ Python/frozen_modules/_sitebuiltins.h: Lib/_sitebuiltins.py $(FREEZE_MODULE_DEPS Python/frozen_modules/genericpath.h: Lib/genericpath.py $(FREEZE_MODULE_DEPS) $(FREEZE_MODULE) genericpath $(srcdir)/Lib/genericpath.py Python/frozen_modules/genericpath.h -Python/frozen_modules/ntpath.h: Lib/ntpath.py $(FREEZE_MODULE_DEPS) - $(FREEZE_MODULE) ntpath $(srcdir)/Lib/ntpath.py Python/frozen_modules/ntpath.h +Python/frozen_modules/ntpath.h: Lib/ntpath/__init__.py $(FREEZE_MODULE_DEPS) + $(FREEZE_MODULE) ntpath $(srcdir)/Lib/ntpath/__init__.py Python/frozen_modules/ntpath.h -Python/frozen_modules/posixpath.h: Lib/posixpath.py $(FREEZE_MODULE_DEPS) - $(FREEZE_MODULE) posixpath $(srcdir)/Lib/posixpath.py Python/frozen_modules/posixpath.h +Python/frozen_modules/ntpath.pure.h: Lib/ntpath/pure.py $(FREEZE_MODULE_DEPS) + $(FREEZE_MODULE) ntpath.pure $(srcdir)/Lib/ntpath/pure.py Python/frozen_modules/ntpath.pure.h + +Python/frozen_modules/posixpath.h: Lib/posixpath/__init__.py $(FREEZE_MODULE_DEPS) + $(FREEZE_MODULE) posixpath $(srcdir)/Lib/posixpath/__init__.py Python/frozen_modules/posixpath.h + +Python/frozen_modules/posixpath.pure.h: Lib/posixpath/pure.py $(FREEZE_MODULE_DEPS) + $(FREEZE_MODULE) posixpath.pure $(srcdir)/Lib/posixpath/pure.py Python/frozen_modules/posixpath.pure.h Python/frozen_modules/os.h: Lib/os.py $(FREEZE_MODULE_DEPS) $(FREEZE_MODULE) os $(srcdir)/Lib/os.py Python/frozen_modules/os.h diff --git a/Misc/NEWS.d/next/Library/2024-06-13-10-12-04.gh-issue-119671.-D116h.rst b/Misc/NEWS.d/next/Library/2024-06-13-10-12-04.gh-issue-119671.-D116h.rst new file mode 100644 index 00000000000000..5b53368bbb242d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-13-10-12-04.gh-issue-119671.-D116h.rst @@ -0,0 +1 @@ +Add the :mod:`ntpath.pure`, :mod:`os.path.pure`, :mod:`posixpath.pure` modules for low-level path manipulation. diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index e5e18de60ec349..1e0c01700e2172 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -318,16 +318,26 @@ $(IntDir)genericpath.g.h $(GeneratedFrozenModulesDir)Python\frozen_modules\genericpath.h - + ntpath $(IntDir)ntpath.g.h $(GeneratedFrozenModulesDir)Python\frozen_modules\ntpath.h - + + ntpath.pure + $(IntDir)ntpath.pure.g.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\ntpath.pure.h + + posixpath $(IntDir)posixpath.g.h $(GeneratedFrozenModulesDir)Python\frozen_modules\posixpath.h + + posixpath.pure + $(IntDir)posixpath.pure.g.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\posixpath.pure.h + os $(IntDir)os.g.h @@ -410,7 +420,9 @@ + + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 9630f54ae4ea29..6fdd19d3748bc9 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -523,10 +523,16 @@ Python Files - + Python Files - + + Python Files + + + Python Files + + Python Files diff --git a/Python/frozen.c b/Python/frozen.c index 627f2ff9413562..a356088a915f5f 100644 --- a/Python/frozen.c +++ b/Python/frozen.c @@ -51,7 +51,9 @@ #include "frozen_modules/_sitebuiltins.h" #include "frozen_modules/genericpath.h" #include "frozen_modules/ntpath.h" +#include "frozen_modules/ntpath.pure.h" #include "frozen_modules/posixpath.h" +#include "frozen_modules/posixpath.pure.h" #include "frozen_modules/os.h" #include "frozen_modules/site.h" #include "frozen_modules/stat.h" @@ -82,9 +84,12 @@ static const struct _frozen stdlib_modules[] = { {"_collections_abc", _Py_M___collections_abc, (int)sizeof(_Py_M___collections_abc), false}, {"_sitebuiltins", _Py_M___sitebuiltins, (int)sizeof(_Py_M___sitebuiltins), false}, {"genericpath", _Py_M__genericpath, (int)sizeof(_Py_M__genericpath), false}, - {"ntpath", _Py_M__ntpath, (int)sizeof(_Py_M__ntpath), false}, - {"posixpath", _Py_M__posixpath, (int)sizeof(_Py_M__posixpath), false}, - {"os.path", _Py_M__posixpath, (int)sizeof(_Py_M__posixpath), false}, + {"ntpath", _Py_M__ntpath, (int)sizeof(_Py_M__ntpath), true}, + {"ntpath.pure", _Py_M__ntpath_pure, (int)sizeof(_Py_M__ntpath_pure), false}, + {"posixpath", _Py_M__posixpath, (int)sizeof(_Py_M__posixpath), true}, + {"posixpath.pure", _Py_M__posixpath_pure, (int)sizeof(_Py_M__posixpath_pure), false}, + {"os.path", _Py_M__posixpath, (int)sizeof(_Py_M__posixpath), true}, + {"os.path.pure", _Py_M__posixpath_pure, (int)sizeof(_Py_M__posixpath_pure), false}, {"os", _Py_M__os, (int)sizeof(_Py_M__os), false}, {"site", _Py_M__site, (int)sizeof(_Py_M__site), false}, {"stat", _Py_M__stat, (int)sizeof(_Py_M__stat), false}, @@ -116,7 +121,8 @@ const struct _frozen *_PyImport_FrozenTest = test_modules; static const struct _module_alias aliases[] = { {"_frozen_importlib", "importlib._bootstrap"}, {"_frozen_importlib_external", "importlib._bootstrap_external"}, - {"os.path", "posixpath"}, + {"os.path", "', + 'ntpath.pure', + '', + 'posixpath.pure', + # We must explicitly mark os.path and os.path.pure as frozen modules + # even though they will never be imported. + f'<{OS_PATH}> : os.path', + f'{OS_PATH}.pure : os.path.pure', 'os', 'site', 'stat',