Skip to content

Commit

Permalink
GH-127807: pathlib ABCs: remove PathBase._unsupported_msg() (#127855)
Browse files Browse the repository at this point in the history
This method helped us customise the `UnsupportedOperation` message
depending on the type. But we're aiming to make `PathBase` a proper ABC
soon, so `NotImplementedError` is the right exception to raise there.
  • Loading branch information
barneygale authored Dec 12, 2024
1 parent 487fdbe commit 7146f18
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 44 deletions.
4 changes: 1 addition & 3 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
operating systems.
"""

from pathlib._abc import *
from pathlib._local import *

__all__ = (_abc.__all__ +
_local.__all__)
__all__ = _local.__all__
35 changes: 11 additions & 24 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@
from pathlib._os import copyfileobj


__all__ = ["UnsupportedOperation"]


class UnsupportedOperation(NotImplementedError):
"""An exception that is raised when an unsupported operation is attempted.
"""
pass


@functools.cache
def _is_case_sensitive(parser):
return parser.normcase('Aa') == 'Aa'
Expand Down Expand Up @@ -353,8 +344,8 @@ class PathBase(PurePathBase):
This class provides dummy implementations for many methods that derived
classes can override selectively; the default implementations raise
UnsupportedOperation. The most basic methods, such as stat() and open(),
directly raise UnsupportedOperation; these basic methods are called by
NotImplementedError. The most basic methods, such as stat() and open(),
directly raise NotImplementedError; these basic methods are called by
other methods such as is_dir() and read_text().
The Path class derives this class to implement local filesystem paths.
Expand All @@ -363,16 +354,12 @@ class PathBase(PurePathBase):
"""
__slots__ = ()

@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"

def stat(self, *, follow_symlinks=True):
"""
Return the result of the stat() system call on this path, like
os.stat() does.
"""
raise UnsupportedOperation(self._unsupported_msg('stat()'))
raise NotImplementedError

# Convenience functions for querying the stat results

Expand Down Expand Up @@ -448,7 +435,7 @@ def open(self, mode='r', buffering=-1, encoding=None,
Open the file pointed to by this path and return a file object, as
the built-in open() function does.
"""
raise UnsupportedOperation(self._unsupported_msg('open()'))
raise NotImplementedError

def read_bytes(self):
"""
Expand Down Expand Up @@ -498,7 +485,7 @@ def iterdir(self):
The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included.
"""
raise UnsupportedOperation(self._unsupported_msg('iterdir()'))
raise NotImplementedError

def _glob_selector(self, parts, case_sensitive, recurse_symlinks):
if case_sensitive is None:
Expand Down Expand Up @@ -575,14 +562,14 @@ def readlink(self):
"""
Return the path to which the symbolic link points.
"""
raise UnsupportedOperation(self._unsupported_msg('readlink()'))
raise NotImplementedError

def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
raise UnsupportedOperation(self._unsupported_msg('symlink_to()'))
raise NotImplementedError

def _symlink_to_target_of(self, link):
"""
Expand All @@ -595,7 +582,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
raise UnsupportedOperation(self._unsupported_msg('mkdir()'))
raise NotImplementedError

# Metadata keys supported by this path type.
_readable_metadata = _writable_metadata = frozenset()
Expand All @@ -604,13 +591,13 @@ def _read_metadata(self, keys=None, *, follow_symlinks=True):
"""
Returns path metadata as a dict with string keys.
"""
raise UnsupportedOperation(self._unsupported_msg('_read_metadata()'))
raise NotImplementedError

def _write_metadata(self, metadata, *, follow_symlinks=True):
"""
Sets path metadata from the given dict with string keys.
"""
raise UnsupportedOperation(self._unsupported_msg('_write_metadata()'))
raise NotImplementedError

def _copy_metadata(self, target, *, follow_symlinks=True):
"""
Expand Down Expand Up @@ -687,7 +674,7 @@ def _delete(self):
"""
Delete this file or directory (including all sub-directories).
"""
raise UnsupportedOperation(self._unsupported_msg('_delete()'))
raise NotImplementedError

def move(self, target):
"""
Expand Down
37 changes: 29 additions & 8 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@

from pathlib._os import (copyfile, file_metadata_keys, read_file_metadata,
write_file_metadata)
from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
from pathlib._abc import PurePathBase, PathBase


__all__ = [
"UnsupportedOperation",
"PurePath", "PurePosixPath", "PureWindowsPath",
"Path", "PosixPath", "WindowsPath",
]


class UnsupportedOperation(NotImplementedError):
"""An exception that is raised when an unsupported operation is attempted.
"""
pass


class _PathParents(Sequence):
"""This object provides sequence-like access to the logical ancestors
of a path. Don't try to construct it yourself."""
Expand Down Expand Up @@ -527,10 +534,6 @@ class Path(PathBase, PurePath):
"""
__slots__ = ()

@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported on this system"

def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
Expand Down Expand Up @@ -817,7 +820,8 @@ def owner(self, *, follow_symlinks=True):
"""
Return the login name of the file owner.
"""
raise UnsupportedOperation(self._unsupported_msg('owner()'))
f = f"{type(self).__name__}.owner()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if grp:
def group(self, *, follow_symlinks=True):
Expand All @@ -831,14 +835,22 @@ def group(self, *, follow_symlinks=True):
"""
Return the group name of the file gid.
"""
raise UnsupportedOperation(self._unsupported_msg('group()'))
f = f"{type(self).__name__}.group()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if hasattr(os, "readlink"):
def readlink(self):
"""
Return the path to which the symbolic link points.
"""
return self.with_segments(os.readlink(self))
else:
def readlink(self):
"""
Return the path to which the symbolic link points.
"""
f = f"{type(self).__name__}.readlink()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

def touch(self, mode=0o666, exist_ok=True):
"""
Expand Down Expand Up @@ -989,6 +1001,14 @@ def symlink_to(self, target, target_is_directory=False):
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
os.symlink(target, self, target_is_directory)
else:
def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
f = f"{type(self).__name__}.symlink_to()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if os.name == 'nt':
def _symlink_to_target_of(self, link):
Expand All @@ -1013,7 +1033,8 @@ def hardlink_to(self, target):
Note the order of arguments (self, target) is the reverse of os.link's.
"""
raise UnsupportedOperation(self._unsupported_msg('hardlink_to()'))
f = f"{type(self).__name__}.hardlink_to()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def needs_symlinks(fn):
_tests_needing_symlinks.add(fn.__name__)
return fn



class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))


#
# Tests for the pure classes.
#
Expand Down
12 changes: 3 additions & 9 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import stat
import unittest

from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
from pathlib._abc import PurePathBase, PathBase
from pathlib._types import Parser
import posixpath

Expand All @@ -27,11 +27,6 @@ def needs_windows(fn):
return fn


class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError))

#
# Tests for the pure classes.
#
Expand Down Expand Up @@ -1294,10 +1289,9 @@ def test_is_absolute_windows(self):
class PathBaseTest(PurePathBaseTest):
cls = PathBase

def test_unsupported_operation(self):
P = self.cls
def test_not_implemented_error(self):
p = self.cls('')
e = UnsupportedOperation
e = NotImplementedError
self.assertRaises(e, p.stat)
self.assertRaises(e, p.exists)
self.assertRaises(e, p.is_dir)
Expand Down

0 comments on commit 7146f18

Please sign in to comment.