diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index b6507eb4d6fa2c..7d4e380470545b 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -173,6 +173,36 @@ we also call *flavours*: *pathsegments* is specified similarly to :class:`PurePath`. +.. class:: SimplePath(*pathsegments) + + A subclassable alternative to the factory :class:`PurePath`. While different + in name, when instantiated, it will otherwise behave similiarly to either + :class:`PurePosixPath` or :class:`PureWindowsPath` depending on the + respective system type on which it is being run. + + *pathsegments* is specified similarly to :class:`PurePath`. + + >>> PurePath('prog.txt').with_suffix('.py') # On Windows + PureWindowsPath('prog.py') + >>> class MySimplePath(SimplePath): pass + ... + >>> MySimplePath('prog.txt').with_suffix('.py') + MySimplePath('prog.py') + +.. class:: SimplePosixPath(*pathsegments) + + A subclassable alternative to :class:`PurePosixPath` which otherwise behaves + similarly. + + *pathsegments* is specified similarly to :class:`PurePath`. + +.. class:: SimpleWindowsPath(*pathsegments) + + A subclassable alternative to :class:`PurePosixPath` which otherwise behaves + similarly. + + *pathsegments* is specified similarly to :class:`PurePath`. + Regardless of the system you're running on, you can instantiate all of these classes, since they don't provide any operation that does system calls. @@ -268,6 +298,92 @@ property: (note how the drive and local root are regrouped in a single part) +Extensibility and Subclassing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Due to the factory functionality of :class:`PurePath` and :class:`Path`, +subclassing them is not supported. However there are alternative classes +:class:`SimplePath` and :class:`FilePath` which can act as direct +replacements and which are subclassable. + +They test equivalently:: + + >>> PurePath('C:/Windows') == SimplePath('C:/Windows') + True + >>> Path('C:/Users') == FilePath('C:/Users') + True + +Moreover, for all methods and attributes they return similar (only +possibly differing in class name) values:: + + >>> PurePath('C:/Windows').drive == SimplePath('C:/Windows').drive + True + >>> Path('C:/Users').is_dir() == FilePath('C:/Users').is_dir() + True + +Note that SimplePath and FilePath are not factories that return a +different class. Instead they implement the type of class that would be +returned by the factory on your particular system type. For instance on +Windows:: + + >>> type(PurePath('.')) # On Windows + + >>> type(SimplePath('.')) + + +Similarly on Posix-based systems:: + + >>> type(Path('.')) # on Posix + + >>> type(FilePath('.')) + + +However unlike :class:`PurePath` and :class:`Path`, :class:`SimplePath` +and :class:`FilePath` can be subclassed and still function equivalently:: + + >>> class MySimplePath(SimplePath): pass # On Windows + ... + >>> MySimplePath('C:/Users').parent + MySimplePath('C:/') + >>> class MyFilePath(FilePath): pass + ... + >>> MyFilePath('C:/Windows/System32/drivers/etc/../..').resolve() + MyFilePath('C:/Windows/System32') + +Furthermore, if you are subclassing paths for a specific system type, +but which don't require accessing the filesystem, much like with +:class:`PurePath` you can use the derivatives of :class:`SimplePath`, +:class:`SimplePosixPath` or :class:`SimpleWindowsPath`:: + + >>> PureWindowsPath('C:/').drive + 'C:' + >>> MySimpleWindowsPath(SimpleWindowsPath): pass + ... + >>> MySimpleWindowsPath('C:/').drive + 'C:' + +If you want to write a subclass but do need to customize how the +methods that do access the filesystem are implemented, you can instead +use the :class:`PathIOMixin`. + +.. class:: PathIOMixin + + A mixin which provides a default filesystem I/O implementation for + Posix and Windows. It requires being used in conjunction with either + :class:`SimplePosixPath` or :class:`SimpleWindowsPath` to create a + full-fledged :class:`Path`/:class:`FilePath`-like class. This class + is actually the provider for all of the methods that are exclusively + available to :class:`Path` family and :class:`FilePath`. As such all + of these methods are available here to be extended by you. + + >>> MyRemoteServerIOMixin(PathIOMixin): + ... def iterdir(self): + ... ... # Override method to provide implementation + ... + >>> MyRemoteServerPath(SimplePosixPath, MyRemoteServerIOMixin): pass + ... + + Methods and properties ^^^^^^^^^^^^^^^^^^^^^^ @@ -675,6 +791,24 @@ bugs or failures in your application):: % (cls.__name__,)) NotImplementedError: cannot instantiate 'WindowsPath' on your system +.. class:: FilePath(*pathsegments) + + A subclass of :class:`SimplePath`, this is a subclassable alternative to + the factory :class:`Path`. It represents concrete paths of the system's + path flavour. + While different in name, when instantiated, it will otherwise behave + similarly to :class:`PosixPath` or :class:`Windows`Path` depending + on the respective system type on which it is being run.) + + *pathsegments* is specified similarly to :class:`PurePath`. + + >>> class MyFilePath(FilePath): pass # On Posix + ... + >>> MyFilePath('usr/local/bin/../../').resolve() + MyFilePath('/usr') + >>> Path('/usr/local/bin/../..').resolve() + PosixPath('/usr') + Methods ^^^^^^^ diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 8e6eb48b9767ca..8c81c6d3fa74e0 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -17,6 +17,8 @@ __all__ = [ "PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath", + "SimplePath", "SimplePosixPath", "SimpleWindowsPath", + "FilePath", "PathIOMixin", ] # @@ -35,6 +37,7 @@ _WINERROR_INVALID_NAME, _WINERROR_CANT_RESOLVE_FILENAME) + def _ignore_error(exception): return (getattr(exception, 'errno', None) in _IGNORED_ERROS or getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) @@ -325,21 +328,24 @@ def touch(self, path, mode=0o666, exist_ok=True): readlink = os.readlink else: def readlink(self, path): - raise NotImplementedError("os.readlink() not available on this system") + raise NotImplementedError("os.readlink() is not available " + "on this system") def owner(self, path): try: import pwd return pwd.getpwuid(self.stat(path).st_uid).pw_name except ImportError: - raise NotImplementedError("Path.owner() is unsupported on this system") + raise NotImplementedError(f"{self.__class__.__name__}.owner() " + f"is unsupported on this system") def group(self, path): try: import grp return grp.getgrgid(self.stat(path).st_gid).gr_name except ImportError: - raise NotImplementedError("Path.group() is unsupported on this system") + raise NotImplementedError(f"{self.__class__.__name__}.group() " + f"is unsupported on this system") getcwd = os.getcwd @@ -490,10 +496,6 @@ def _select_from(self, parent_path, is_dir, exists, scandir): return -# -# Public API -# - class _PathParents(Sequence): """This object provides sequence-like access to the logical ancestors of a path. Don't try to construct it yourself.""" @@ -525,28 +527,39 @@ def __repr__(self): return "<{}.parents>".format(self._pathcls.__name__) -class PurePath(object): - """Base class for manipulating paths without I/O. +class _PurePathBase(object): + """ + Base class for manipulating paths without I/O. - PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. + Classes deriving from _PurePathBase represent filesystem paths and + inherit methods that can be used for path manipulation but which + don't perform any actual filesystem I/O. """ + __slots__ = ( '_drv', '_root', '_parts', '_str', '_hash', '_pparts', '_cached_cparts', ) + @classmethod + def __init_subclass__(cls, **kwargs): + mro_without_obj = cls.mro()[:-1] + mro_names = [class_.__name__ for class_ in mro_without_obj] + if "PurePath" not in mro_names: + has_flavour = False + for class_ in mro_without_obj: + if hasattr(class_, "_flavour"): + has_flavour = True + break + if not has_flavour: + is_posix = os.name == "posix" + cls._flavour = _posix_flavour if is_posix else _windows_flavour + return cls + def __new__(cls, *args): - """Construct a PurePath from one or several strings and or existing - PurePath objects. The strings and path objects are combined so as - to yield a canonicalized path, which is incorporated into the - new PurePath object. - """ - if cls is PurePath: - cls = PureWindowsPath if os.name == 'nt' else PurePosixPath + # Utilize __new__ rather __init__ in order to provide a setup + # method in common with PurePath and Path which are derived from + # this base class and require extra flexibility. return cls._from_parts(args) def __reduce__(self): @@ -560,7 +573,7 @@ def _parse_args(cls, args): # canonicalize some constructor arguments. parts = [] for a in args: - if isinstance(a, PurePath): + if isinstance(a, cls): parts += a._parts else: a = os.fspath(a) @@ -649,9 +662,12 @@ def _cparts(self): return self._cached_cparts def __eq__(self, other): - if not isinstance(other, PurePath): + if not isinstance(other, _PurePathBase): return NotImplemented - return self._cparts == other._cparts and self._flavour is other._flavour + return ( + self._cparts == other._cparts + and self._flavour is other._flavour + ) def __hash__(self): try: @@ -661,22 +677,34 @@ def __hash__(self): return self._hash def __lt__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: + if ( + not isinstance(other, _PurePathBase) + or self._flavour is not other._flavour + ): return NotImplemented return self._cparts < other._cparts def __le__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: + if ( + not isinstance(other, _PurePathBase) + or self._flavour is not other._flavour + ): return NotImplemented return self._cparts <= other._cparts def __gt__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: + if ( + not isinstance(other, _PurePathBase) + or self._flavour is not other._flavour + ): return NotImplemented return self._cparts > other._cparts def __ge__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: + if ( + not isinstance(other, _PurePathBase) + or self._flavour is not other._flavour + ): return NotImplemented return self._cparts >= other._cparts @@ -902,54 +930,40 @@ def match(self, path_pattern): return False return True -# Can't subclass os.PathLike from PurePath and keep the constructor -# optimizations in PurePath._parse_args(). -os.PathLike.register(PurePath) - -class PurePosixPath(PurePath): - """PurePath subclass for non-Windows systems. +class _PosixMixin: + """ + Provides Posix flavour for PurePath/SimplePath and derivatives - On a POSIX system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. + Requires _PurePathBase in the mro. """ _flavour = _posix_flavour __slots__ = () -class PureWindowsPath(PurePath): - """PurePath subclass for Windows systems. +class _WindowsMixin: + """ + Provides Windows flavour for PurePath/SimplePath and derivatives - On a Windows system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. + Requires _PurePathBase in the mro. """ _flavour = _windows_flavour __slots__ = () -# Filesystem-accessing classes - - -class Path(PurePath): - """PurePath subclass that can make system calls. - - Path represents a filesystem path but unlike PurePath, also offers - methods to do system calls on path objects. Depending on your system, - instantiating a Path will return either a PosixPath or a WindowsPath - object. You can also instantiate a PosixPath or WindowsPath directly, - but cannot instantiate a WindowsPath on a POSIX system or vice versa. +class PathIOMixin: + """ + Provides the default I/O operations for Posix and Windows. + + This mixin provides the methods used by FilePath and Path + derivatives in order to perform filesystem I/O. This can also be used + externally for custom Path/FilePath-like classes. To do so it must + be used in conjunction with either SimplePosixPath or + SimpleWindowsPath as it requires _PurePathBase to be in the method + resolution order. """ - _accessor = _normal_accessor - __slots__ = () - def __new__(cls, *args, **kwargs): - if cls is Path: - cls = WindowsPath if os.name == 'nt' else PosixPath - self = cls._from_parts(args) - if not self._flavour.is_supported: - raise NotImplementedError("cannot instantiate %r on your system" - % (cls.__name__,)) - return self + _accessor = _normal_accessor def _make_child_relpath(self, part): # This is an optimization used for dir walking. `part` must be @@ -965,7 +979,7 @@ def __exit__(self, t, v, tb): # In previous versions of pathlib, this method marked this path as # closed; subsequent attempts to perform I/O would raise an IOError. # This functionality was never documented, and had the effect of - # making Path objects mutable, contrary to PEP 428. In Python 3.9 the + # making path objects mutable, contrary to PEP 428. In Python 3.9 the # _closed attribute was removed, and this method made a no-op. # This method and __enter__()/__exit__() should be deprecated and # removed in the future. @@ -1012,7 +1026,7 @@ def glob(self, pattern): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern. """ - sys.audit("pathlib.Path.glob", self, pattern) + sys.audit(f"pathlib.{self.__class__.__name__}.glob", self, pattern) if not pattern: raise ValueError("Unacceptable pattern: {!r}".format(pattern)) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) @@ -1027,7 +1041,7 @@ def rglob(self, pattern): directories) matching the given relative pattern, anywhere in this subtree. """ - sys.audit("pathlib.Path.rglob", self, pattern) + sys.audit(f"pathlib.{self.__class__.__name__}.rglob", self, pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) if drv or root: raise NotImplementedError("Non-relative patterns are unsupported") @@ -1215,9 +1229,9 @@ def rename(self, target): The target path may be absolute or relative. Relative paths are interpreted relative to the current working directory, *not* the - directory of the Path object. + directory of this object. - Returns the new Path instance pointing to the target path. + Returns the new class instance pointing to the target path. """ self._accessor.rename(self, target) return self.__class__(target) @@ -1228,9 +1242,9 @@ def replace(self, target): The target path may be absolute or relative. Relative paths are interpreted relative to the current working directory, *not* the - directory of the Path object. + directory of this object. - Returns the new Path instance pointing to the target path. + Returns the new class instance pointing to the target path. """ self._accessor.replace(self, target) return self.__class__(target) @@ -1256,15 +1270,16 @@ def link_to(self, target): Note this function does not make this path a hard link to *target*, despite the implication of the function and argument names. The order - of arguments (target, link) is the reverse of Path.symlink_to, but + of arguments (target, link) is the reverse of symlink_to, but matches that of os.link. Deprecated since Python 3.10 and scheduled for removal in Python 3.12. Use `hardlink_to()` instead. """ - warnings.warn("pathlib.Path.link_to() is deprecated and is scheduled " - "for removal in Python 3.12. " - "Use pathlib.Path.hardlink_to() instead.", + classname = self.__class__.__name__ + warnings.warn(f"pathlib.{classname}.link_to() is deprecated and is " + f"scheduled for removal in Python 3.12. " + f"Use pathlib.{classname}.hardlink_to() instead.", DeprecationWarning, stacklevel=2) self._accessor.link(self, target) @@ -1322,6 +1337,9 @@ def is_mount(self): """ Check if this path is a POSIX mount point """ + if os.name != "posix": + raise NotImplementedError(f"{self.__class__.__name__}.is_mount() " + f"is unsupported on this system") # Need to exist and be a dir if not self.exists() or not self.is_dir(): return False @@ -1431,6 +1449,83 @@ def expanduser(self): return self +# +# Public API +# + + +class PurePath(_PurePathBase): + """ + Factory to generate instances for manipulating paths without I/O. + + Instantiating PurePath returns an instance of either PureWindowsPath + or PurePosixPath in its place depending on the flavour of system + present. The resultant class can then be used for path manipulations + but will not have any methods which make calls to the filesystem. + Alternatively, you can also instantiate either of these classes + directly, regardless of your system flavour. + """ + + def __new__(cls, *args): + """ + Construct a PurePath from string(s) and/or existing PurePaths + + The strings and path objects are combined so as to yield a + canonicalized path, which is incorporated into the new PurePath + object. + """ + if cls is PurePath: + cls = PureWindowsPath if os.name == 'nt' else PurePosixPath + return cls._from_parts(args) + + +# Can't subclass os.PathLike from PurePath and keep the constructor +# optimizations in PurePath._parse_args(). +os.PathLike.register(PurePath) + + +class PurePosixPath(PurePath, _PosixMixin): + """PurePath subclass for non-Windows systems. + + On a POSIX system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + pass + + +class PureWindowsPath(PurePath, _WindowsMixin): + """PurePath subclass for Windows systems. + + On a Windows system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + pass + + +# Filesystem-accessing classes + + +class Path(PurePath, PathIOMixin): + """PurePath subclass that can make system calls. + + Path represents a filesystem path but unlike PurePath, also offers + methods to do system calls on path objects. Depending on your system, + instantiating a Path will return either a PosixPath or a WindowsPath + object. You can also instantiate a PosixPath or WindowsPath directly, + but cannot instantiate a WindowsPath on a POSIX system or vice versa. + """ + __slots__ = () + + def __new__(cls, *args, **kwargs): + if cls is Path: + cls = WindowsPath if os.name == 'nt' else PosixPath + self = cls._from_parts(args) + if not self._flavour.is_supported: + raise NotImplementedError("cannot instantiate %r on your system" + % (cls.__name__,)) + return self + + class PosixPath(Path, PurePosixPath): """Path subclass for non-Windows systems. @@ -1438,6 +1533,7 @@ class PosixPath(Path, PurePosixPath): """ __slots__ = () + class WindowsPath(Path, PureWindowsPath): """Path subclass for Windows systems. @@ -1445,5 +1541,48 @@ class WindowsPath(Path, PureWindowsPath): """ __slots__ = () - def is_mount(self): - raise NotImplementedError("Path.is_mount() is unsupported on this system") + +class SimplePath(_PurePathBase): + """ + Class for manipulating paths without I/O. + + SimplePath represents a filesystem path and offers operations + which don't perform any actual filesystem I/O. The methods in this + class will return values that are appropriate for your system + flavour, be that Windows or Posix, though the return values are not + guaranteed to be the same for both platforms. + """ + pass + + +class SimplePosixPath(_PurePathBase, _PosixMixin): + """ + Class for manipulating Posix paths without I/O. + + SimplePosixPath represents a Posix specific filesystem path and + offers operations which don't perform any actual filesystem I/O. + """ + pass + + +class SimpleWindowsPath(_PurePathBase, _WindowsMixin): + """ + Class for manipulating Windows paths without I/O. + + SimpleWindowsPath represents a Windows specific filesystem path and + offers operations which don't perform any actual filesystem I/O. + """ + pass + + +class FilePath(SimplePath, PathIOMixin): + """ + SimplePath subclass that can make system calls. + + FilePath represents a filesystem path but unlike SimplePath, also + offers methods to do system calls on path objects. The methods in + this class will return values that are appropriate for your system + flavour, be that Windows or Posix, though the return values are not + guaranteed to be the same for both platforms. + """ + pass diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 54b7977b43f235..03d56e366bc4d4 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -21,6 +21,12 @@ grp = pwd = None +only_nt = unittest.skipIf(os.name != 'nt', + 'test requires a Windows-compatible system') +only_posix = unittest.skipIf(os.name == 'nt', + 'test requires a POSIX-compatible system') + + class _BaseFlavourTest(object): def _check_parse_parts(self, arg, expected): @@ -354,7 +360,9 @@ def test_ordering_common(self): # Ordering is tuple-alike. def assertLess(a, b): self.assertLess(a, b) + self.assertLessEqual(a, b) self.assertGreater(b, a) + self.assertGreaterEqual(b, a) P = self.cls a = P('a') b = P('a/b') @@ -701,6 +709,11 @@ def test_pickling_common(self): class PurePosixPathTest(_BasePurePathTest, unittest.TestCase): cls = pathlib.PurePosixPath + def test_flavour(self): + P = self.cls + self.assertEqual(P._flavour.__class__, + pathlib._PosixFlavour) + def test_root(self): P = self.cls self.assertEqual(P('/a/b').root, '/') @@ -790,6 +803,11 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): ], }) + def test_flavour(self): + P = self.cls + self.assertEqual(P._flavour.__class__, + pathlib._WindowsFlavour) + def test_str(self): p = self.cls('a/b/c') self.assertEqual(str(p), 'a\\b\\c') @@ -1296,6 +1314,7 @@ def test_is_reserved(self): # UNC paths are never reserved. self.assertIs(False, P('//my/share/nul/con/aux').is_reserved()) + class PurePathTest(_BasePurePathTest, unittest.TestCase): cls = pathlib.PurePath @@ -1322,6 +1341,52 @@ def test_different_flavours_unordered(self): p >= q +class SubclassTestMixin: + + def test_can_subclass(self): + P = self.cls + try: + class Derived(P): + pass + except Exception as e: + self.fail(f"Failed to subclass {P}: {e}") + else: + self.assertTrue(issubclass(Derived, P)) + try: + derived = Derived() + except Exception as e: + self.fail("Failed to be able to instantiate a class " + f"derived from {P}: {e}") + else: + self.assertIsInstance(derived, Derived) + + +class SimplePathTest(_BasePurePathTest, SubclassTestMixin, unittest.TestCase): + cls = pathlib.SimplePath + + +@only_posix +class SimplePathAsPurePosixPathTest(PurePosixPathTest, unittest.TestCase): + cls = pathlib.SimplePath + + +@only_nt +class SimplePathAsPureWindowsPathTest(PureWindowsPathTest, unittest.TestCase): + cls = pathlib.SimplePath + + +class SimplePosixPathAsPurePosixPathTest( + PurePosixPathTest, SubclassTestMixin, unittest.TestCase +): + cls = pathlib.SimplePosixPath + + +class SimpleWindowsPathAsPureWindowsPathTest( + PureWindowsPathTest, SubclassTestMixin, unittest.TestCase +): + cls = pathlib.SimpleWindowsPath + + # # Tests for the concrete classes. # @@ -1331,15 +1396,12 @@ def test_different_flavours_unordered(self): join = lambda *x: os.path.join(BASE, *x) rel_join = lambda *x: os.path.join(TESTFN, *x) -only_nt = unittest.skipIf(os.name != 'nt', - 'test requires a Windows-compatible system') -only_posix = unittest.skipIf(os.name == 'nt', - 'test requires a POSIX-compatible system') @only_posix class PosixPathAsPureTest(PurePosixPathTest): cls = pathlib.PosixPath + @only_nt class WindowsPathAsPureTest(PureWindowsPathTest): cls = pathlib.WindowsPath @@ -1355,6 +1417,20 @@ def test_group(self): P('c:/').group() +@only_posix +class FilePathAsPurePosixTest(PurePosixPathTest): + cls = pathlib.FilePath + + +@only_nt +class FilePathAsPureWindowsTest(PureWindowsPathTest): + cls = pathlib.FilePath + + +class FilePathAsPureTest(_BasePurePathTest, unittest.TestCase): + cls = pathlib.FilePath + + class _BasePathTest(object): """Tests for the FS-accessing functionalities of the Path classes.""" @@ -2666,6 +2742,20 @@ def check(): check() +class FilePathTest(_BasePathTest, SubclassTestMixin, unittest.TestCase): + cls = pathlib.FilePath + + +@only_posix +class FilePathAsPosixPathTest(PosixPathTest): + cls = pathlib.FilePath + + +@only_nt +class FilePathAsWindowsPathTest(WindowsPathTest): + cls = pathlib.FilePath + + class CompatiblePathTest(unittest.TestCase): """ Test that a type can be made compatible with PurePath diff --git a/Misc/ACKS b/Misc/ACKS index b023bcb6d72fd3..553eb1a75bab37 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -545,6 +545,7 @@ Matt Fleming Hernán Martínez Foffani Benjamin Fogle Artem Fokin +Kevin Follstad Arnaud Fontaine Michael Foord Amaury Forgeot d'Arc diff --git a/Misc/NEWS.d/next/Library/2021-05-25-21-25-39.bpo-24132.37mJNj.rst b/Misc/NEWS.d/next/Library/2021-05-25-21-25-39.bpo-24132.37mJNj.rst new file mode 100644 index 00000000000000..88dc3f50150133 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-25-21-25-39.bpo-24132.37mJNj.rst @@ -0,0 +1,5 @@ +Add a drop-in, extensible alternative to :class:`pathlib.PurePath` named +:class:`pathlib.SimplePath`, which has accompanying classes +:class:`pathlib.SimplePosixPath` and :class:`pathlib.SimpleWindowsPath`. +Also add extensible alternative to :class:`pathlib.Path` named +:class:`pathlib.FilePath`.