mirror of https://github.com/python/cpython
GH-89812: Add `pathlib.UnsupportedOperation` (GH-105926)
This new exception type is raised instead of `NotImplementedError` when a path operation is not supported. It can be raised from `Path.readlink()`, `symlink_to()`, `hardlink_to()`, `owner()` and `group()`. In a future version of pathlib, it will be raised by `AbstractPath` for these methods and others, such as `AbstractPath.mkdir()` and `unlink()`.
This commit is contained in:
parent
04492cbc9a
commit
a8006706f7
|
@ -88,6 +88,17 @@ Opening a file::
|
||||||
'#!/bin/bash\n'
|
'#!/bin/bash\n'
|
||||||
|
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. exception:: UnsupportedOperation
|
||||||
|
|
||||||
|
An exception inheriting :exc:`NotImplementedError` that is raised when an
|
||||||
|
unsupported operation is called on a path object.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
|
|
||||||
.. _pure-paths:
|
.. _pure-paths:
|
||||||
|
|
||||||
Pure paths
|
Pure paths
|
||||||
|
@ -752,6 +763,11 @@ calls on path objects. There are three ways to instantiate concrete paths:
|
||||||
|
|
||||||
*pathsegments* is specified similarly to :class:`PurePath`.
|
*pathsegments* is specified similarly to :class:`PurePath`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` on Windows. In previous versions,
|
||||||
|
:exc:`NotImplementedError` was raised instead.
|
||||||
|
|
||||||
|
|
||||||
.. class:: WindowsPath(*pathsegments)
|
.. class:: WindowsPath(*pathsegments)
|
||||||
|
|
||||||
A subclass of :class:`Path` and :class:`PureWindowsPath`, this class
|
A subclass of :class:`Path` and :class:`PureWindowsPath`, this class
|
||||||
|
@ -762,6 +778,11 @@ calls on path objects. There are three ways to instantiate concrete paths:
|
||||||
|
|
||||||
*pathsegments* is specified similarly to :class:`PurePath`.
|
*pathsegments* is specified similarly to :class:`PurePath`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` on non-Windows platforms. In previous
|
||||||
|
versions, :exc:`NotImplementedError` was raised instead.
|
||||||
|
|
||||||
|
|
||||||
You can only instantiate the class flavour that corresponds to your system
|
You can only instantiate the class flavour that corresponds to your system
|
||||||
(allowing system calls on non-compatible path flavours could lead to
|
(allowing system calls on non-compatible path flavours could lead to
|
||||||
bugs or failures in your application)::
|
bugs or failures in your application)::
|
||||||
|
@ -778,7 +799,7 @@ bugs or failures in your application)::
|
||||||
File "<stdin>", line 1, in <module>
|
File "<stdin>", line 1, in <module>
|
||||||
File "pathlib.py", line 798, in __new__
|
File "pathlib.py", line 798, in __new__
|
||||||
% (cls.__name__,))
|
% (cls.__name__,))
|
||||||
NotImplementedError: cannot instantiate 'WindowsPath' on your system
|
UnsupportedOperation: cannot instantiate 'WindowsPath' on your system
|
||||||
|
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
|
@ -952,6 +973,10 @@ call fails (for example because the path doesn't exist).
|
||||||
Return the name of the group owning the file. :exc:`KeyError` is raised
|
Return the name of the group owning the file. :exc:`KeyError` is raised
|
||||||
if the file's gid isn't found in the system database.
|
if the file's gid isn't found in the system database.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` if the :mod:`grp` module is not
|
||||||
|
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.is_dir()
|
.. method:: Path.is_dir()
|
||||||
|
|
||||||
|
@ -1210,6 +1235,10 @@ call fails (for example because the path doesn't exist).
|
||||||
Return the name of the user owning the file. :exc:`KeyError` is raised
|
Return the name of the user owning the file. :exc:`KeyError` is raised
|
||||||
if the file's uid isn't found in the system database.
|
if the file's uid isn't found in the system database.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` if the :mod:`pwd` module is not
|
||||||
|
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.read_bytes()
|
.. method:: Path.read_bytes()
|
||||||
|
|
||||||
|
@ -1252,6 +1281,10 @@ call fails (for example because the path doesn't exist).
|
||||||
|
|
||||||
.. versionadded:: 3.9
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` if :func:`os.readlink` is not
|
||||||
|
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.rename(target)
|
.. method:: Path.rename(target)
|
||||||
|
|
||||||
|
@ -1414,6 +1447,11 @@ call fails (for example because the path doesn't exist).
|
||||||
The order of arguments (link, target) is the reverse
|
The order of arguments (link, target) is the reverse
|
||||||
of :func:`os.symlink`'s.
|
of :func:`os.symlink`'s.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` if :func:`os.symlink` is not
|
||||||
|
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.hardlink_to(target)
|
.. method:: Path.hardlink_to(target)
|
||||||
|
|
||||||
Make this path a hard link to the same file as *target*.
|
Make this path a hard link to the same file as *target*.
|
||||||
|
@ -1424,6 +1462,10 @@ call fails (for example because the path doesn't exist).
|
||||||
|
|
||||||
.. versionadded:: 3.10
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raises :exc:`UnsupportedOperation` if :func:`os.link` is not
|
||||||
|
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.touch(mode=0o666, exist_ok=True)
|
.. method:: Path.touch(mode=0o666, exist_ok=True)
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,10 @@ built on debug mode <debug-build>`.
|
||||||
pathlib
|
pathlib
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
* Add :exc:`pathlib.UnsupportedOperation`, which is raised instead of
|
||||||
|
:exc:`NotImplementedError` when a path operation isn't supported.
|
||||||
|
(Contributed by Barney Gale in :gh:`89812`.)
|
||||||
|
|
||||||
* Add support for recursive wildcards in :meth:`pathlib.PurePath.match`.
|
* Add support for recursive wildcards in :meth:`pathlib.PurePath.match`.
|
||||||
(Contributed by Barney Gale in :gh:`73435`.)
|
(Contributed by Barney Gale in :gh:`73435`.)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ from urllib.parse import quote_from_bytes as urlquote_from_bytes
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"UnsupportedOperation",
|
||||||
"PurePath", "PurePosixPath", "PureWindowsPath",
|
"PurePath", "PurePosixPath", "PureWindowsPath",
|
||||||
"Path", "PosixPath", "WindowsPath",
|
"Path", "PosixPath", "WindowsPath",
|
||||||
]
|
]
|
||||||
|
@ -207,6 +208,13 @@ def _select_unique(paths):
|
||||||
# Public API
|
# Public API
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class UnsupportedOperation(NotImplementedError):
|
||||||
|
"""An exception that is raised when an unsupported operation is called on
|
||||||
|
a path object.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _PathParents(Sequence):
|
class _PathParents(Sequence):
|
||||||
"""This object provides sequence-like access to the logical ancestors
|
"""This object provides sequence-like access to the logical ancestors
|
||||||
of a path. Don't try to construct it yourself."""
|
of a path. Don't try to construct it yourself."""
|
||||||
|
@ -1241,7 +1249,7 @@ class Path(PurePath):
|
||||||
import pwd
|
import pwd
|
||||||
return pwd.getpwuid(self.stat().st_uid).pw_name
|
return pwd.getpwuid(self.stat().st_uid).pw_name
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise NotImplementedError("Path.owner() is unsupported on this system")
|
raise UnsupportedOperation("Path.owner() is unsupported on this system")
|
||||||
|
|
||||||
def group(self):
|
def group(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1252,14 +1260,14 @@ class Path(PurePath):
|
||||||
import grp
|
import grp
|
||||||
return grp.getgrgid(self.stat().st_gid).gr_name
|
return grp.getgrgid(self.stat().st_gid).gr_name
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise NotImplementedError("Path.group() is unsupported on this system")
|
raise UnsupportedOperation("Path.group() is unsupported on this system")
|
||||||
|
|
||||||
def readlink(self):
|
def readlink(self):
|
||||||
"""
|
"""
|
||||||
Return the path to which the symbolic link points.
|
Return the path to which the symbolic link points.
|
||||||
"""
|
"""
|
||||||
if not hasattr(os, "readlink"):
|
if not hasattr(os, "readlink"):
|
||||||
raise NotImplementedError("os.readlink() not available on this system")
|
raise UnsupportedOperation("os.readlink() not available on this system")
|
||||||
return self.with_segments(os.readlink(self))
|
return self.with_segments(os.readlink(self))
|
||||||
|
|
||||||
def touch(self, mode=0o666, exist_ok=True):
|
def touch(self, mode=0o666, exist_ok=True):
|
||||||
|
@ -1363,7 +1371,7 @@ class Path(PurePath):
|
||||||
Note the order of arguments (link, target) is the reverse of os.symlink.
|
Note the order of arguments (link, target) is the reverse of os.symlink.
|
||||||
"""
|
"""
|
||||||
if not hasattr(os, "symlink"):
|
if not hasattr(os, "symlink"):
|
||||||
raise NotImplementedError("os.symlink() not available on this system")
|
raise UnsupportedOperation("os.symlink() not available on this system")
|
||||||
os.symlink(target, self, target_is_directory)
|
os.symlink(target, self, target_is_directory)
|
||||||
|
|
||||||
def hardlink_to(self, target):
|
def hardlink_to(self, target):
|
||||||
|
@ -1373,7 +1381,7 @@ class Path(PurePath):
|
||||||
Note the order of arguments (self, target) is the reverse of os.link's.
|
Note the order of arguments (self, target) is the reverse of os.link's.
|
||||||
"""
|
"""
|
||||||
if not hasattr(os, "link"):
|
if not hasattr(os, "link"):
|
||||||
raise NotImplementedError("os.link() not available on this system")
|
raise UnsupportedOperation("os.link() not available on this system")
|
||||||
os.link(target, self)
|
os.link(target, self)
|
||||||
|
|
||||||
def expanduser(self):
|
def expanduser(self):
|
||||||
|
@ -1400,7 +1408,7 @@ class PosixPath(Path, PurePosixPath):
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
raise NotImplementedError(
|
raise UnsupportedOperation(
|
||||||
f"cannot instantiate {cls.__name__!r} on your system")
|
f"cannot instantiate {cls.__name__!r} on your system")
|
||||||
|
|
||||||
class WindowsPath(Path, PureWindowsPath):
|
class WindowsPath(Path, PureWindowsPath):
|
||||||
|
@ -1412,5 +1420,5 @@ class WindowsPath(Path, PureWindowsPath):
|
||||||
|
|
||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
raise NotImplementedError(
|
raise UnsupportedOperation(
|
||||||
f"cannot instantiate {cls.__name__!r} on your system")
|
f"cannot instantiate {cls.__name__!r} on your system")
|
||||||
|
|
|
@ -24,6 +24,12 @@ except ImportError:
|
||||||
grp = pwd = None
|
grp = pwd = None
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedOperationTest(unittest.TestCase):
|
||||||
|
def test_is_notimplemented(self):
|
||||||
|
self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
|
||||||
|
self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))
|
||||||
|
|
||||||
|
|
||||||
# Make sure any symbolic links in the base test path are resolved.
|
# Make sure any symbolic links in the base test path are resolved.
|
||||||
BASE = os.path.realpath(TESTFN)
|
BASE = os.path.realpath(TESTFN)
|
||||||
join = lambda *x: os.path.join(BASE, *x)
|
join = lambda *x: os.path.join(BASE, *x)
|
||||||
|
@ -1550,12 +1556,12 @@ class WindowsPathAsPureTest(PureWindowsPathTest):
|
||||||
|
|
||||||
def test_owner(self):
|
def test_owner(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(pathlib.UnsupportedOperation):
|
||||||
P('c:/').owner()
|
P('c:/').owner()
|
||||||
|
|
||||||
def test_group(self):
|
def test_group(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(pathlib.UnsupportedOperation):
|
||||||
P('c:/').group()
|
P('c:/').group()
|
||||||
|
|
||||||
|
|
||||||
|
@ -2055,6 +2061,13 @@ class PathTest(unittest.TestCase):
|
||||||
with self.assertRaises(OSError):
|
with self.assertRaises(OSError):
|
||||||
(P / 'fileA').readlink()
|
(P / 'fileA').readlink()
|
||||||
|
|
||||||
|
@unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present")
|
||||||
|
def test_readlink_unsupported(self):
|
||||||
|
P = self.cls(BASE)
|
||||||
|
p = P / 'fileA'
|
||||||
|
with self.assertRaises(pathlib.UnsupportedOperation):
|
||||||
|
q.readlink(p)
|
||||||
|
|
||||||
def _check_resolve(self, p, expected, strict=True):
|
def _check_resolve(self, p, expected, strict=True):
|
||||||
q = p.resolve(strict)
|
q = p.resolve(strict)
|
||||||
self.assertEqual(q, expected)
|
self.assertEqual(q, expected)
|
||||||
|
@ -2343,7 +2356,7 @@ class PathTest(unittest.TestCase):
|
||||||
if self.cls._flavour is os.path:
|
if self.cls._flavour is os.path:
|
||||||
self.skipTest("path flavour is supported")
|
self.skipTest("path flavour is supported")
|
||||||
else:
|
else:
|
||||||
self.assertRaises(NotImplementedError, self.cls)
|
self.assertRaises(pathlib.UnsupportedOperation, self.cls)
|
||||||
|
|
||||||
def _test_cwd(self, p):
|
def _test_cwd(self, p):
|
||||||
q = self.cls(os.getcwd())
|
q = self.cls(os.getcwd())
|
||||||
|
@ -2543,12 +2556,12 @@ class PathTest(unittest.TestCase):
|
||||||
self.assertTrue(link2.exists())
|
self.assertTrue(link2.exists())
|
||||||
|
|
||||||
@unittest.skipIf(hasattr(os, "link"), "os.link() is present")
|
@unittest.skipIf(hasattr(os, "link"), "os.link() is present")
|
||||||
def test_link_to_not_implemented(self):
|
def test_hardlink_to_unsupported(self):
|
||||||
P = self.cls(BASE)
|
P = self.cls(BASE)
|
||||||
p = P / 'fileA'
|
p = P / 'fileA'
|
||||||
# linking to another path.
|
# linking to another path.
|
||||||
q = P / 'dirA' / 'fileAA'
|
q = P / 'dirA' / 'fileAA'
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(pathlib.UnsupportedOperation):
|
||||||
q.hardlink_to(p)
|
q.hardlink_to(p)
|
||||||
|
|
||||||
def test_rename(self):
|
def test_rename(self):
|
||||||
|
@ -2776,6 +2789,15 @@ class PathTest(unittest.TestCase):
|
||||||
self.assertTrue(link.is_dir())
|
self.assertTrue(link.is_dir())
|
||||||
self.assertTrue(list(link.iterdir()))
|
self.assertTrue(list(link.iterdir()))
|
||||||
|
|
||||||
|
@unittest.skipIf(hasattr(os, "symlink"), "os.symlink() is present")
|
||||||
|
def test_symlink_to_unsupported(self):
|
||||||
|
P = self.cls(BASE)
|
||||||
|
p = P / 'fileA'
|
||||||
|
# linking to another path.
|
||||||
|
q = P / 'dirA' / 'fileAA'
|
||||||
|
with self.assertRaises(pathlib.UnsupportedOperation):
|
||||||
|
q.symlink_to(p)
|
||||||
|
|
||||||
def test_is_junction(self):
|
def test_is_junction(self):
|
||||||
P = self.cls(BASE)
|
P = self.cls(BASE)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :exc:`pathlib.UnsupportedOperation`, which is raised instead of
|
||||||
|
:exc:`NotImplementedError` when a path operation isn't supported.
|
Loading…
Reference in New Issue